java学习笔记-多线程

目录:
一、创建及启动线程
二、线程状态切换
三、线程安全(volatile,reentrantLock,syncrhoized)
四、线程池
五、concurrent 包

一、创建及启动线程

1. 实现Runnable接口

1.定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体
2.创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象
3.通过调用线程对象的start()方法来启动线程

1
2
3
4
5
6
7
public  class Runner1 implements Runnable {
public void run(){
for(int i=0;i<100;i++) {
System.out.println("runner1: " +i);
}
}
}
1
2
3
4
5
6
7
8
9
public static void main(String args[]) {
Runner1 r1 = new Runner1();
//r1.run();此处视为方法调用,等run方法执行完毕后才进行main()方法操作。
Thread t = new Thread(r1);
t.start();
for (int i = 0; i < 10; i++) {
System.out.println("main thread:" + i);
}
}

此时新线程和主线程交替打印。

2. 继承thread类

1.定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。
2.创建Thread子类的实例,也就是创建了线程对象
3.调用线程的start()方法启动线程

1
2
3
4
5
6
7
public class Runner2 extends Thread {
public void run(){
for(int i=0;i<100;i++) {
System.out.println("runner2: " +i);
}
}
}

1
2
3
4
5
6
7
8
9
10
public static void main(String args[] ){

Runner2 r2=new Runner2();
r2.start();

for (int i = 0; i < 10; i++) {
System.out.println("main thread:" + i);
}

}

3. 实现callable接口

1.创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
2.使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
3.使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
4.调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
 public static void main(String[] args) {
Runner3 td = new Runner3();
FutureTask<Integer> result = new FutureTask<>(td);
new Thread(result).start();
try {
Integer sum = result.get();
//FutureTask 可用于 闭锁 类似于CountDownLatch的作用,在所有的线程没有执行完成之后这里是不会执行的
System.out.println(sum);
System.out.println("------------------------------------");
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Runner3 implements Callable<Integer> {

@Override
public Integer call() throws Exception {
int sum = 0;

for (int i = 0; i <= 100000; i++) {
sum += i;
}
return sum;
}

}

二、线程状态切换

1. sleep()

sleep()方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态。可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是sleep()方法不会释放“锁标志”,也就是说如果有synchronized同步块,其他线程仍然不能访问共享数据。

2.yield()

 yield()方法和sleep()方法类似,也不会释放“锁标志”,区别在于,它没有参数,即yield()方法只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行,另外yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也和sleep()方法不同。

3.join()

join()方法会使当前线程等待调用join()方法的线程结束后才能继续执行.

4.notify()&notifyALL()

先说两个概念:锁池和等待池
● 锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
● 等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中。
然后再来说notify和notifyAll的区别:
● 如果线程调用了对象的wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
● 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
综上,所谓唤醒线程,另一种解释可以说是将线程由等待池移动到锁池,notifyAll调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而notify只会唤醒一个线程。有了这些理论基础,后面的notify可能会导致死锁,而notifyAll则不会的例子也就好解释了。

三、线程安全(volatile,reentrantLock,syncrhoized)

四、线程池

五、concurrent 包