------- android培训、java培训、期待与您交流! ----------
一、概念
3,创建Thread类的子类对象,其实就是在创建线程,调用start方法。
多线程的运行出现了安全问题。
问题的原因:
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
静态进内存室,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
类名.class 该对象的类型是Class
静态的同步方法,使用的锁是该方法所在类的字节码文件对象。 类名.class
notifyAll();
只有一种,run方法结束。开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。
进程:是一个正在执行中的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
线程:就是进程中的一个独立的控制单元,线程在控制着进程的执行,一个进程中,至少有一个线程。
Java VM 启动的时候,至少有两个线程:线程运行的代码存在于main方法中,该线程称之为主线程;JVM垃圾回收线程
扩展知识:其实更细节说明, jvm启动不止一个线程,还有负责垃圾回收机制的线程。
二、线程的创建
1、第一种方式:继承Thread类
1.1该类中定义了,创建线程对象的方法(构造函数),提供了要被线程执行的代码存储的位置(run());还定义了开启线程运行的方法(start()),同时还有一些其他的方法用于操作线程:
static Thread currentThead()://返回对当前正在执行的线程对象的引用。String getName()://返回该线程的名称static void sleep(time)throws InterruptedException://在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
要运行的代码都是后期定义的。所以创建线程的第一种方式是:继承Thread类。原因:要覆盖run方法,定义线程要运行的代码。
1.2创建步骤:
1,继承Thread类。
2,覆盖run方法。将线程要运行的代码定义其中。
3,创建Thread类的子类对象,其实就是在创建线程,调用start方法。
package com.xiancheng.Thread;
class Demo extends Thread{public void run(){for ( int x=0;x<60;x++)System. out .println(Thread.currentThread().getName()+ "----" +x);}}
public class ThreadDemo{public static void main(String[] agrs){Demo d = new Demo(); //创建好一个线程。d.start(); //开启线程并执行该线程的run方法。//d.run();//仅仅是对象调用方法。而线程创建,并没有执行。new Demo().start();//主线程for ( int i=0;i<100;i++){System. out .println(Thread.currentThread().getName()+ "----" +i);}}}
发现运行结果每一次都不同。因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。明确一点,在某一个时刻,只能有一个程序在运行。(多核除外),cpu在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象把多线程的运行行为在互相抢夺cpu的执行权。
这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说的算。
为什么要覆盖run方法呢?
Thread类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法。也就是说Thread类中的run方法,用于存储线程要运行的代码。
2、第二种方式: 实现Runable接口
2.1步骤:
1,定义类实现Runnable接口2,覆盖Runnable接口中的run方法。将线程要运行的代码存放在该run方法中。3,通过Thread类建立线程对象。4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。为什么要将Runnable接口的子类对象传递给Thread的构造函数。因为,自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象。5,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
package com.xiancheng.Thread;
class Demo implements Runnable{public void run(){for ( int x=0;x<60;x++)System. out .println(Thread.currentThread().getName()+ "----" +x);}}
public class ThreadDemo{public static void main(String[] agrs){/*Demo d = new Demo();//创建好一个Runnable接口的子类对象。Thread t = new Thread(d);//创建了一个线程;t.start();*/new Thread( new Demo()).start();//主线程for ( int i=0;i<100;i++){System. out .println(Thread.currentThread().getName()+ "----" +i);}}}
实现方式和继承方式有什么区别呢?
实现方式好处:避免了单继承的局限性。在定义线程时,建立使用实现方式。
两种方式区别:
继承Thread:线程代码存放Thread子类run方法中。
实现Runnable,线程代码存在接口的子类的run方法。
三、线程的四种状态
线程在被创建之后,调用线程的start方法,线程运行,运行结束后消亡,在运行过程中,如果调用sleep,或wait方法,线程处于冻结状态,放弃了执行资格,当线程被唤醒(notify),或sleep时间到,线程会进入临时状态,或阻塞状态,该状态具备运行资格,但没有执行权。
wait()和sleep()有何区别?
wait()释放资源、释放锁;sleep()释放资源、不释放锁。
四、线程安全
package com.xiancheng.Thread;
class Demo implements Runnable{private int tick = 100;public void run(){while ( true ){if ( tick >0){try {Thread.sleep(10);} catch (Exception e){}System. out .println(Thread.currentThread().getName()+ "....sale : " + tick --);}}}}
public class ThreadDemo{public static void main(String[] agrs){Demo d = new Demo();new Thread(d).start();new Thread(d).start();}}
/*.....Thread-1....sale : 0*/
通过分析,发现,打印出0错票。
多线程的运行出现了安全问题。
问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
1、同步
同步的原理:就是将部分操作功能数据的代码进行加锁。
同步的前提:
1,必须要有两个或者两个以上的线程。2,必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源,同步的表现形式:
1,同步代码块。
2,同步函数。
两者有什么不同:
同步代码块使用的锁是任意对象。
同步函数使用的锁是this。
注意:对于static的同步函数,使用的锁不是this。是 类名.class 是该类的字节码文件对象。
1.1同步代码块
对象如同锁。持有锁的线程可以在同步中执行。
没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
synchronized(对象)
{
需要被同步的代码
}
{
需要被同步的代码
}
如何确定哪些代码是需要被同步的代码:
1,明确哪些代码是多线程运行代码。2,明确共享数据。3,明确多线程运行代码中哪些语句是操作共享数据的。
1.2同步函数
public synchronized void method(Type args) {
所以同步函数使用的锁是this。
需要被同步的语句;
}
问题1:同步函数用的是哪一个锁呢?
函数需要被对象调用。那么函数都有一个所属对象引用。就是this。
所以同步函数使用的锁是this。
实例验证:
需求:使用两个线程来买票。一个线程在同步代码块中。一个线程在同步函数中。都在执行买票动作。
class Ticket implements Runnable{private int tick = 100;Object obj = new Object();boolean flag = true ;public void run(){if ( flag ){while ( true ){synchronized (obj){if ( tick >0){try {Thread.sleep(10);} catch (Exception e){}System. out .println(Thread.currentThread().getName()+ "....code : " + tick --);}}}}elsewhile ( true )show();}public synchronized void show() //this{if ( tick >0){try {Thread.sleep(10);} catch (Exception e){}System. out .println(Thread.currentThread().getName()+ "....show.... : " + tick --);}}}
public class ThisLockDemo{public static void main(String[] args){
Ticket t = new Ticket();
Thread t1 = new Thread(t);Thread t2 = new Thread(t);t1.start();try {Thread.sleep(10);} catch (Exception e){}t. flag = false ;t2.start();
}}
/*以上代码会出现0票。
*/
问题2:如果同步函数被静态修饰后,使用的锁是什么呢?
静态进内存室,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
类名.class 该对象的类型是Class
静态的同步方法,使用的锁是该方法所在类的字节码文件对象。 类名.class
class Ticket implements Runnable{private static int tick = 100;Object obj = new Object();boolean flag = true ;public void run(){if ( flag ){while ( true ){synchronized ( obj ){if ( tick >0){try {Thread.sleep(10);} catch (Exception e){}System. out .println(Thread.currentThread().getName()+ "....code : " + tick --);}}}}elsewhile ( true )show();}public static synchronized void show()//Ticket.class{if ( tick >0){try {Thread.sleep(10);} catch (Exception e){}System. out .println(Thread.currentThread().getName()+ "....show.... : " + tick --);}}}
public class StaticMethodDemo{public static void main(String[] args){
Ticket t = new Ticket();
Thread t1 = new Thread(t);Thread t2 = new Thread(t);t1.start();try {Thread.sleep(10);} catch (Exception e){}t. flag = false ;t2.start();
}}
/*以上代码会出现0票。
*/
2、单例模式
饿汉式。
class Single{private static final Single s = new Single();private Single(){}public static Single getInstance(){return s;}懒汉试}
class Single
{
private static Single s = null;
private Single(){}
public static Single getInstance()
{
if(s==null)
{ //双重判断减少对锁的判断次数
synchronized(Single.class)
{
if(s==null)
//--->A;
s = new Single();
}
}
return s;
}
}
饿汉式和懒汉式有什么区别?
1、懒汉式特点:实例的延时加载。
2、懒汉式在多线程访问时存在安全问题(为什么?答:获取实例的方法中有多条语句操作共享资源),如何解决?
答:用同步代码块和同步函数都能解决此问题,但效率稍低;可以用双重判断来减少判断锁的次数。
3、懒汉式加同步时使用的锁是哪个?
答:该类所属的字节码文件对象。
3、死锁(同步中嵌套同步,但使用的锁不一样。)
package com.xiancheng.Thread;
class DeadTest implements Runnable{private boolean flag ; //定义一个标记DeadTest( boolean flag){this . flag = flag;}public void run(){if ( flag ){synchronized (MyLock. locka ){System. out .println( "if locka" );synchronized (MyLock. lockb ){System. out .println( "if lockb" );}}}else{synchronized (MyLock. lockb ){System. out .println( "else lockb" );synchronized (MyLock. locka ){System. out .println( "else locka" );}}}}}
class MyLock{//定义两个锁static Object locka = new Object();static Object lockb = new Object();}
public class DeadLockTest {
public static void main(String[] args) {Thread d1 = new Thread( new DeadTest( true ));Thread d2 = new Thread( new DeadTest( false ));
}
}
五、线程间通信(多个线程在操作同一个资源,但是操作的动作不同。)
1、以下代码就是简单的两个线程在操作同一个资源,但是运行以上程序会出现安全问题,需要加入同步。
package com.xiancheng.Thread;
class Res{String name ;String sex ;}
class Input implements Runnable{private Res r ;Input(Res r){this . r = r;}public void run(){int x = 0;while ( true ){//synchronized(r)//{if (x==0){r . name = "mike" ;r . sex = "man" ;}else{r . name = "丽丽" ;r . sex = "女女女女女" ;}x = (x+1)%2;//}}}}
class Output implements Runnable{private Res r ;Output(Res r){this . r = r;}public void run(){while ( true ){//synchronized(r)//{System. out .println( r . name + "...." + r . sex );//}}}}
public class InputOutDemo{public static void main(String[] args){Res r = new Res();
Input in = new Input(r);Output out = new Output(r);
Thread t1 = new Thread(in);Thread t2 = new Thread(out);
t1.start();t2.start();}}
/*丽丽....女女女女女丽丽....女女女女女mike....女女女女女mike....女女女女女mike....manmike....女女女女女丽丽....女女女女女丽丽....man丽丽....man丽丽....女女女女女*/
加锁/*丽丽....女女女女女丽丽....女女女女女丽丽....女女女女女丽丽....女女女女女
mike....manmike....manmike....manmike....manmike....manmike....man*/
以上代码加上锁,会发现成段的输出丽丽或者mike(CPU切换造成的,out不会只输出一次,会一直输出,只到CPU切换),input按正常情况应该是存一次、打印一次。
2、 所以就需要用到等待、唤醒机制。input执行后等待(wait),output执行;output完成后,唤醒(notify)input,output等待。
package com.xiancheng.Thread;
class Res{String name ;String sex ;boolean flag = false ;}
class Input implements Runnable{private Res r ;Input(Res r){this . r = r;}public void run(){int x = 0;while ( true ){synchronized ( r ){if ( r . flag ){try {r .wait(); //等待线程存在于线程池,唤醒的线程都是线程池中的。} catch (InterruptedException e) {e.printStackTrace();}}if (x==0){r . name = "mike" ;r . sex = "man" ;}else{r . name = "丽丽" ;r . sex = "女女女女女" ;}x = (x+1)%2;r . flag = true ;r .notify();}}}}
class Output implements Runnable{private Res r ;Output(Res r){this . r = r;}public void run(){while ( true ){synchronized ( r ){if (! r . flag ){try {r .wait();} catch (InterruptedException e) {e.printStackTrace();}}System. out .println( r . name + "...." + r . sex );r . flag = false ;r .notify();}}}}
public class InputOutDemo{public static void main(String[] args){Res r = new Res();
Input in = new Input(r);Output out = new Output(r);
Thread t1 = new Thread(in);Thread t2 = new Thread(out);
t1.start();t2.start();}}
wait:
notify();
notifyAll();
都使用在同步中,因为要对持有监视器(锁)的线程操作,只有同步才具有锁。
为什么这些操作线程的方法要定义Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。
3、 notifyAll案例:
package com.xiancheng.Thread;
public class ProducerConsumerDemo{public static void main(String[] args){Resource r = new Resource();
Producer pro = new Producer(r);Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);Thread t2 = new Thread(pro);Thread t3 = new Thread(con);Thread t4 = new Thread(con);
t1.start();t2.start();t3.start();t4.start();
}}
class Resource{private String name ;private int count = 1;private boolean flag = false ;// t1 t2public synchronized void set(String name){while ( flag )try { this .wait();} catch (Exception e){} //t1(放弃资格) t2(获取资格)this . name = name+ "--" + count ++;
System. out .println(Thread.currentThread().getName()+ "...生产者.." + this . name );flag = true ;this .notifyAll();}
// t3 t4public synchronized void out(){while (! flag )try {wait();} catch (Exception e){} //t3(放弃资格) t4(放弃资格)System. out .println(Thread.currentThread().getName()+ "...消费者........." + this . name );flag = false ;this .notifyAll();}}
class Producer implements Runnable{private Resource res ;
Producer(Resource res){this . res = res;}public void run(){while ( true ){res .set( "+商品+" );}}}
class Consumer implements Runnable{private Resource res ;
Consumer(Resource res){this . res = res;}public void run(){while ( true ){res .out();}}}
对于多个生产者和消费者,为什么要定义while判断标记?
原因:让被唤醒的线程再一次判断标记。
但是使用while导致程序会挂起,
为什么定义notifyAll,而不是notify
因为需要唤醒对方线程,只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。
因为需要唤醒对方线程,只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。
4、lock接口和Condition接口
JDK1.5 中提供了多线程升级解决方案。
将同步Synchronized替换成现实Lock操作。将Object中的wait,notify notifyAll,替换了Condition对象。该对象可以Lock锁 进行获取。实现了本方只唤醒对方操作。
Lock:替代了SynchronizedlockunlocknewCondition()
Condition:替代了Object wait notify notifyAllawait();signal();signalAll();
package com.xiancheng.Thread;
import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;
class Resource{private String name ;private int count = 1;private boolean flag = false ;private Lock lock = new ReentrantLock();//创建多个对象,一个为生产者,一个为消费者private Condition condition_pro = lock .newCondition();private Condition condition_con = lock .newCondition();
public void set(String name) throws InterruptedException{lock .lock(); //生产者获取锁try{while ( flag )condition_pro .await(); //条件成立生产者等待。this . name = name+ "--" + count ++;
System. out .println(Thread.currentThread().getName()+ "...生产者.." + this . name );flag = true ;condition_con .signal(); //唤醒消费者}finally{lock .unlock(); //释放锁的动作一定要执行。}}
public void out() throws InterruptedException{lock .lock(); //消费者获取锁try{while (! flag )condition_con .await(); //条件满足,消费者等待。System. out .println(Thread.currentThread().getName()+ "...消费者........." + this . name );flag = false ;condition_pro .signal(); //唤醒生产者}finally{lock .unlock();}}}
5、线程方法
5.1程停止线
特殊情况:当线程处于了冻结状态,就不会读取到标记,那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态是,这时需要对冻结进行清除。强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。Thread类提供该方法 interrupt();
package com.xiancheng.Thread;
class StopThread implements Runnable{private boolean flag = true ;public synchronized void run(){while ( flag ){try{wait();} catch (InterruptedException e){flag = false ; //操作标记让线程结束}System. out .println(Thread.currentThread().getName()+ "....run" );}}public void changeFlag(){flag = false ; //操作标记让线程结束}}
public class StopThreadDemo{public static void main(String[] args){StopThread st = new StopThread();Thread t1 = new Thread(st);Thread t2 = new Thread(st);
t1.start();t2.start();
int num = 0;
while ( true ){if (num++ == 60){//st.changeFlag();t1.interrupt();//清除线程的冻结状态t2.interrupt();break ;}System. out .println(Thread.currentThread().getName()+ "......." +num);}System. out .println( "over" );}}
5.2守护线程
setDaemon(true):设置线程为守护线程(也可将其称为后台线程)。当正在运行的线程都是守护线程时,jvm即退出。该方法必须在启动线程前调用。
前台线程和后台线程:在执行过程中没有区别(同样抢占cpu的执行权),只在结束时有区别。守护线程依赖于主线程(前台线程);当主线程结束后,所有守护线程自动结束。
5.3合并线程(join)
当A线程执行到了B线程的.join()方法时,A就会等待。等B线程都执行完,A才会执行。join可以用来临时加入线程执行。
5.4优先级
线程优先级(默认为5,共10级),setPriority(Thread.MAX_PRIORITY)设置优先级最高
5.5Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。减缓进程的执行频率。临时释放用的,是线程平均得到执行权。
六、什么时候用多线程?
当某些代码需要同时被执行时,就用单独的线程进行封装。两种方式则其一即可
class ThreadTest{public static void main(String[] args){new Thread(){public void run(){for ( int x=0; x<100; x++){System. out .println(Thread.currentThread().getName()+ "....." +x);}}}.start();
for ( int x=0; x<100; x++){System. out .println(Thread.currentThread().getName()+ "....." +x);}
Runnable r = new Runnable(){public void run(){for ( int x=0; x<100; x++){System. out .println(Thread.currentThread().getName()+ "....." +x);}}};new Thread(r).start();
//new Test1().start();}}
七、分析程序
class MyThread extends Thread{ public void run(){ try { Thread.currentThread().sleep(3000); } catch (InterruptedException e) { } System.out.println("MyThread running"); } } public class ThreadTest{ public static void main(String argv[]) { MyThread t = new MyThread(); t.run(); t.start(); System.out.println("Thread Test"); } } |
MyThread t = new MyThread();创建了一个线程。
t.run();调用MyThread对象的run方法。这是只有一个线程在运行就是主线程。当主线程执行到了run方法中的sleep(3000);时。这是主线程处于冻结状态。程序并没有任何执行。当3秒过后,主线程打印了 MyThread running。 run方法执行结束。
t.start();开启了t线程。有两种可能情况:
第一种,主线程在只执行了t.start()后,还具有执行权,继续往下执行,打印了Thread Test。主线程结束。t线程获取执行权,调用自己的run方法。然后执行的sleep(3000);冻结3秒。3秒后,打印MyThread running t线程结束,整个程序结束。
第二种情况:主线程执行到t.start();开启了t线程,t线程就直接获取到了执行权。就调用自己的run方法。指定到sleep(3000).t线程冻结3秒,这是t线程就是释放了执行权。那么主线程开始执行打印了Thread Test,主线程结束。等到3秒后,t线程打印MyThread running ,然后t线程结束。程序结束。