多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销;;多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
新建状态:使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
就绪状态:当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
运行状态:如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
阻塞状态:如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
·等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态
·同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
·其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
死亡状态:一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
Java 提供了三种创建线程的方法:
通过实现 Runnable 接口;
通过继承 Thread 类本身;
通过 Callable 和 Future 创建线程。
创建线程的三种方式的对比
1. 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
多线程有几种实现方法?同步有几种实现方法?
多线程有两种实现方法,分别是继承 Thread 类与实现 Runnable 接口
同步的实现方面有两种,分别是 synchronized,wait 与 notify
wait():使一个线程处于等待状态,并且释放所持有的对象的 lock。
sleep(): 使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉
InterruptedException 异常。
notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且不是按优先级。
Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。
wait和sleep区别
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
获取对象锁进入运行状态。
.启动一个线程是用 run()还是 start()? .
启动一个线程是调用 start()方法,使线程就绪状态,以后可以被调度为运行状态,一个线程必须关联一些具体的执行代码,run()方法是该线程所关联的执行代码。
当一个线程进入一个对象的一个 synchronized 方法后,其它线程是否可进入此对象其它方法?
分几种情况:
1.其他方法前是否加了 synchronized 关键字,如果没加,则能。
2.如果这个方法内部调用了 wait,则可以进入其他 synchronized 方法。
3.如果其他个方法都加了 synchronized 关键字,并且内部没有调用 wait,则不能。
4.如果其他方法是 static,它用的同步锁是当前类的字节码,与非静态的方法不能同步,因为非静态的方法用的是 this。
线程:
Thread类中有许多管理线程的方法,包括创建、启动、暂停他们,所有操作都是从run()操作开始的,并且在run()方法内编写需要在独立线程内执行的代码。执行的线程都是通过调用run()开始的。
没有参数的run()方法是自动被调用的,而带参数的run()是被重载的,必须显式的调用。
两种方法:
///这种方式把线程相关的代码和线程要执行的代码分离开来 class ThreadTest implements Runnable{ public void run(){ System.out.println("run here;"); } public static void main(String[] args){ ThreadTest tt=new ThreadTest(); Thread t1=new Thread(tt); Thread t2=new Thread(tt); t1.start();t2.start(); } } | ///另一种方式就是参数式匿名内部类 class ThreadTest { public static void main(String[] args){ Thread tt=new Thread(new Runnable(){ public void run(){ System.out.println("thread;;;"); } } ); tt.start(); } }
|
在调用start()方法前线程还不是活的,即调用isAlive()方法的结果为false
如果已经启动,且还没有完成run()方法,则为true;
Thread类实现了Runnable接口,不是抽象类。;;run()是由线程执行的方法;;
线程同步:
1:共享变量:使用同步使得一个线程正确转到另一个线程
2:存在于同一个内存空间中的所有线程:同一进程中的线程可以共享相同的进程上下文,包括内存空间。只要访问共享变量,线程就可以方便的互相交换数据。但是必须受控访问共享变量
3:受控访问的同步:为实现受控方式访问数据,java中两个关键字:synchronized和volatile
Synchronized:一次只有一个线程可以执行代码受保护的部分;一个线程更改的数据对于其他线程是可见的。
4:确保共享数据更改的可见性:同步可以让用户确保线程看到一致的内存视图;
Volatile比同步更简单,只适合于控制对基本变量的单个实例的访问。当一个变量被声明成volatile,任何对该变量的写操作都会绕过高速缓存,直接写入主内存,读取也是直接读取主内存。这样所有线程在任何时候看到的volatile变量值都是相同的。
5:用锁保护的原子代码块:对每个java对象都由一个相关的锁的概念,同一时间只能有一个线程持有java锁,当线程进入synchronized代码块时,线程会阻塞并等待,直到锁可用。当线程处于就绪状态时,并且获得锁后,将执行代码块,当控制退出受保护的代码块,即到达了代码块末尾或者抛出了没有在synchronized块中捕获的异常,它就会释放该锁。
同步示例:
public class syncExample{ private static lockobject=new Object(); private static class Thread1 extends Thread{ public void run(){ synchronized(lockobject){ x=y=0; System.out.println(x); } } } private static class Thread2 extends Thread{ public void run(){ synchronized(lockobject){ x=y=1; System.out.println(y); } } } public static void main(String[] args) { new Thread1().run(); new Thread2().run(); } } | 在这两个线程中都必须使用同步,使得程序正确执行; 将会打印10或01;如果没有同步还会打印11或00;;
他们两个线程都是锁同个对象 synchronized(lockobject){
|
public class Main extends Thread{ static Object lock1=new Object(); static Object lock2=new Object(); static volatile int i1,i2,j1,j2,k1,k2; public void run() { while(true) { doit(); check(); } } void doit() { synchronized (lock1) { i1++; } j1++; synchronized (lock2) { k1++;k2++; } j2++; synchronized (lock1) { i2++; } } void check() { if(i1!=i2) { System.out.println("i"); } if(j1!=j2) { System.out.println("j"); } if(k1!=k2) { System.out.println("k"); } } public static void main(String[] args) { new Main().start(); new Main().start(); } } | 因为check不依靠任何锁,可以在任何时候执行。而且doit没有原子性,所以check可以在doit中间执行;;所以不能确定会输出哪个i,k,j
|