您的位置:首页 > 编程语言 > Java开发

[置顶] JavaSE学习笔记_10:Java多线程

2016-03-02 00:10 746 查看
Java多线程
 

1、线程概述

我们都知道进程,进程是一个正在执行的程序。如qq,酷狗等,是一个明确的概念。而线程就是进程中的一个独立的控制单元或者叫做一个独立的执行路径,控制着进程的执行,线程包含在进程当中,是一个抽象概念。

 

一个进程中至少有一个线程。

 

我们知道:cpu在不同进程中快速切换,更具体地说,cpu在不同的线程中快速切换。我们现在以一个进程当中有多个线程来学习。多个线程都获取cpu的执行权,cpu执行到谁,或者说被谁抢到,谁就执行,执行多长时间cpu说了算。因此某一个时刻,只能有一个程序在运行(多核除外)。cpu在做着快速的切换,以达到看上去同时运行的效果。cpu切换具有随机性。

 

java也有两个进程:编译进程javac.exe、运行进程java.exe。

class Demo

{

public static void main(String[] args)

{

System.out.println(“hello world”);

}

}

java.exe进程启动后会使jvm启动,jvm就开始执行程序,

像这样一个简单的程序,就只有一个线程负责java程序的执行。而且这个线程运行的代码存在于main()中。该线程称之为主线程。其实,jvm启动的应该不止这一个线程,还有负责垃圾回收的线程。这样效率才高。

 


 

为什么一个程序中需要有多个线程来分工控制执行?

一个程序当中肯定会涉及到外设的工作,因此,当需要外设工作时,线程就应该释放cpu资源,让其他线程使用,那么cpu资源没有浪费,还是在执行这个程序的其他部分。如果是单线程,需要外设工作时,线程就会等待外设工作完以后才继续向下执行,cpu闲置浪费资源。因此完成程序执行的时间拉长,效率低。这就跟多个人配合起来做一件事,效率高是一回事。

 

2、创建线程

(1)第一种方式:继承Thread类

① 定义类继承Thread类。

② 复写Thread类中的run方法。

③ 调用创建对象调用start方法。

Ps:注意区别run()和start()。

start():开启线程并执行该线程的run方法。一个线程只能开启一次。

run():仅仅是对象调用的方法。存放线程要执行的代码。

 

(2)第二种方式:实现Runnable接口

① 定义类实现Runnable接口。

② 覆盖Runnable接口中的run方法,该方法也是存放线程要执行的代码。

③ 通过Thread类建立线程对象。

④ 将Runnable接口的子类对象作为实际参数作为实际参数传递给Thread类的构造函数。

⑤ 调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

 

 

线程的几种状态:

 




 

练习:创建两个线程,和主线程交替运行。x是独有数据。

class Test extends Thread

{

private String name;

Test(String)

{

This.name=name;

}

p
10f64
ublic void run()

{

for(int x=0;x<60;x++)

{

System.out.println(name+”run--”+x);

}

}

}

class ThreadTest

{

public static void main(String[] args)

{

Test t1=new Test(“one”);

Test t2=new Test(“two”);

}

t1.start();

t2.start();

for(int x=0;x<60;x++)

{

System.out.println(“main---”+x);

}

}

 

线程都有自己的默认的名称:

Thread-编号,该编号从0开始。

也可以自定义名称:

Thread 

{

private String name;

Thread(String name)

{

This.name=name;

}

public String getName()

{

return name;

}

}

class Test extends Thread

{

Test(String name)

{

super(name);

}

}

Thread类中还有一个静态方法currentThread(),返回当前正在执行的线程对象的引用。this.getName()=Thread.currentThread().getName()

 

 

 

通过售票的例子来讲解两种创建线程方法的不同:

需求:简单的买票程序。多个窗口卖票。(多线程)票数是共享数据。

class Ticket extends Thread

{

private static int tick=100;

public void run()

{

while(true)

{

if(tick>=0)

{

System.out.println();

}

}

}

}

Class TicketDemo

{

Public static void main(String[] args)

{

Ticket t1=new Ticket();

Ticket t2=new Ticket();

Ticket t3=new Ticket();

Ticket t4=new Ticket();

 

t2.start();

t3.start();

t4.start();

t5.start();

}

}

但是,static变量生命周期过长。因此换一种方式:

class Ticket implements Runnable

{

private int tick=100;

public void run()

{

while(true)

{

if(tick<0)

{

System.out.println(

Thread.currentThread().getName()+”...sale:”+tick--);

}

}

}

}

class TicketDemo

{

public static void main(String[] args)

{

Ticket t=new Ticket();

 

Thread t1=new Thread(t);

Thread t2=new Thread(t);

Thread t3=new Thread(t);

Thread t4=new Thread(t);

 

t2.start();

t3.start();

t4.start();

t5.start();

 

}

}

因此:开发时都建立使用第二种方式。

 

3、多线程的安全问题

看图说话:

 


用程序来模拟这种情况:

class Ticket implements Runnable

{

private int tick=100;

public void run()

{

//线程抢来资源,cpu允许其执行一段时间,因此用用 //循环,让它可以执行多次。

while(true)

{

if(tick<0)

{

try{Thread.sleep(10);}catch(Exception e){}

System.out.println(

Thread.currentThread().getName()+”...sale:”+tick--);

}

}

}

}

/*

一个线程进来以后,睡了10秒钟进入了冻结状态,该线程就不具备执行资格,释放了cpu资源。其他线程抢到了,进来以后,发生了同样的情况。当他们醒来以后,进入了临时状态,随后进入运行状态,就不在判断if条件了直接往下执行。假设它们醒来之前tick的值为2,则醒后分别打印2,1,-1,0。则打印了错票,产生了安全问题。

*/

class TicketDemo

{

public static void main(String[] args)

{

Ticket t=new Ticket();

 

Thread t1=new Thread(t);

Thread t2=new Thread(t);

Thread t3=new Thread(t);

Thread t4=new Thread(t);

 

t2.start();

t3.start();

t4.start();

t5.start();

 

}

}

 

产生安全问题的原因:共享数据

当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致数据的错误。

 

安全问题的解决办法:同步

同步有两种形式:

同步代码块、同步函数

同步代码块:

Sysnchronized(对象)

{

需要被同步的代码。

}

 


同步函数:

在函数返回值类型前面加上关键字:synchronized。

(1)非静态同步函数

它使用的锁是this。(函数需要被对象调用。那么函数都有一个所属对象的引用。就是this,因此为了满足使用同一个锁,同步函数使用的锁是this。)

(2)静态同步函数

它使用的锁是所在类的类文件。(静态进内存时,内存中没有本类对象,但是一定有该类文件对应的字节码文件对象。类名.class,该对象的类型是class)

 

解决售票的这个安全问题可以用同步代码块:

class Ticket implements Runnable

{

private int tick=100;

public void run()

{

while(true)

{

//锁直接用Object类,省得再定义类了。

Synchronized(new Object())

{

if(tick<0)

{

try{Thread.sleep(10);}catch(Exception e){}

System.out.println(

Thread.currentThread().getName()+”...sale:”+tick--);

}

}

}

}

}

class TicketDemo

{

public static void main(String[] args)

{

Ticket t=new Ticket();

 

Thread t1=new Thread(t);

Thread t2=new Thread(t);

Thread t3=new Thread(t);

Thread t4=new Thread(t);

 

t2.start();

t3.start();

t4.start();

t5.start();

}

}

解决售票的这个安全问题也可以用同步函数:

class Ticket implements Runnable

{

private int tick=100;

public void run()

{

while(true)

{

show();

}

}

public synchronized void show()

{

if(tick<0)

{

try{Thread.sleep(10);}catch(Exception e){}

System.out.println(

Thread.currentThread().getName()+”...sale:”+tick--);

}

}

}

class TicketDemo

{

public static void main(String[] args)

{

Ticket t=new Ticket();

 

Thread t1=new Thread(t);

Thread t2=new Thread(t);

Thread t3=new Thread(t);

Thread t4=new Thread(t);

 

t2.start();

t3.start();

t4.start();

t5.start();

}

}

 

 

同步的前提:

a. 必须要有两个或者两个以上的线程。

b. 必须是多个线程使用同一个锁。主要是指将需要同步的代码分成了两处,每一处都不只一条语句,一处用了同步代码块,一处用了同步函数,此时锁是this;还有就是线程通信中两个实现Runnable接口的子类中run()操作共享数据时(此时应该叫同一资源更合适)分别有多条语句且被同步,两处的锁也必须是同一个锁,通常这个锁都是资源类Res对象。

 

Ps:前提是加了同步一定安全的重要依据。加了同步,还有安全问题,就要看是否遵循了这两个前提。

 

同步的好处和弊端:

好处:解决了多线程的安全问题。

弊端:多个线程需要判断锁,较为消耗资源。

 

多线程安全问题的解决思路:

(1)找到问题

① 明确哪些代码是多线程运行代码。

② 明确共享数据。

③ 明确多线程运行代码中那些语句是操作共享数据的。这些语句就是需要被同步的代码。

(2)解决问题

 用同步

 

4、死锁

死锁:同步中嵌套同步。

(面试)

class MyLock

{

static Object locka=new Object();

static Object lockb=new Object();

}

class Test implements Runnable

{

private boolean flag;

Test(boolean flag)

{

This.flag=flag;

}

public void run()

{

if(flag)

{

while(true)

{

synchronized(MyLock.locka)

{

system.out.println(

Thread.currentThread().getName()+”...if locka”)

synchronized(Mylock.lockb)

{

system.out.println(

Thread.currentThread().getName()+”...if lockb”)

 

}

}

}

}

else

{

while(true)

{

synchronized(MyLock.lockb)

{

System.out.println(

Thread.currentThread().getName()+”...else lockb”)

synchronized(Mylock.locka)

{

System.out.println(

Thread.currentThread().getName()+”...else locka”)

}

}

}

}

}

class DeadLockTest

{

public static void main(String[] args)

{

Thread t1=new Thread(new Test(true));

Thread t2=new Thread(new Test(flase));

t1.start();

t2.start();

}

}

 

 

 

5、线程间通信

多个线程动作不一致,执行代码不一样。两个类,两个run()。

比如:

 




线程间通信:

其实就是多个线程在操作同一个资源。

但是操作的动作相反。

 

示例:

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)

{

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)

{

System.out.println(r.name+”...”+r.sex);

}

}

}

class InputOutputDemo

{

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();

}

}

同样,它也会产生安全问题:存完姓名,还没来得及存性别,资源就被输出线程抢走,导致打印信息不对称:(男姓名,女性别)。因此也必须用同步来解决。

如下:

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);

}

}

}

}

class InputOutputDemo

{

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();

}

}

打印的效果不是很爽,原因是:cpu切换造成的,cpu资源时刻分配随机,且执行多长时间不定。输入线程获得cpu执行权后,可能会执行一段时间,那么在这段时间内就有可能存多次数据,且数据被覆盖存储,值为最后一次存的值。对于输出线程亦是如此。现在想存一次打印一次。

则:

 


代码如下:

class Res

{

String name;

String sex;

private 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(Exception e){}

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(Exception e){}

System.out.println(r.name+”...”+r.sex);

r.flag=flag;

r.notify();

}

}

}

}

class InputOutputDemo

{

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();

}

}

代码优化:

class Res

{

String name;

String sex;

private boolean flag=false;

 

public synchronized void set(String name,String sex)

{

if(flag)

try{this.wait();}catch(Exception e){}

this.name=name;

this.sex=sex;

flag=true;

this.notify();

}

 

public synchronized void out()

{

if(!r.flag)

try{r.wait();}catch(Exception e){}

System.out.println(r.name+”...”+r.sex);

flag=false;

this.notify();

 

}

}

class Input implements Runnable

{

private Res r;

Input(Res r)

{

this.r=r;

}

public void run()

{

int x=0;

while(true)

{

 

if(x==0)

{

r.set(“mike”,”man”);

}

else

{

r.set(“丽丽”,”女女”);

}

x=(x+1)%2;

}

}

}

}

class Output implements Runnable

{

private Res r;

Output(Res r)

{

this.r=r;

}

public void run()

{

while(true)

{

r.out();

      }

}

}

}

class InputOutputDemo

{

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();

}

}

拓展:现在有多个输出线程,多个输入线程。

class Res

{

String name;

String sex;

private boolean flag=false;

 

public synchronized void set(String name,String sex)

{

if(flag)

try{this.wait();}catch(Exception e){}

this.name=name;

this.sex=sex;

flag=true;

this.notify();

}

 

public synchronized void out()

{

if(!r.flag)

try{r.wait();}catch(Exception e){}

System.out.println(r.name+”...”+r.sex);

flag=false;

this.notify();

 

}

}

class Input implements Runnable

{

private Res r;

Input(Res r)

{

this.r=r;

}

public void run()

{

int x=0;

while(true)

{

 

if(x==0)

{

r.set(“mike”,”man”);

}

else

{

r.set(“丽丽”,”女女”);

}

x=(x+1)%2;

}

}

}

}

class Output implements Runnable

{

private Res r;

Output(Res r)

{

this.r=r;

}

public void run()

{

while(true)

{

r.out();

      }

}

}

}

class InputOutputDemo

{

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(in);

 

Thread t3=new Thread(out);

Thread t4=new Thread(out);

 

t1.start();

t2.start();

t3.start();

t4.start();

 

}

}

说明:

如果t1线程先获得cpu执行权,则输入一次,将资源置为非空,唤醒等待线程池中的头一个线程,虽然此时没有需要唤醒的线程,然后进入等待状态;然后t2线程获得cpu执行权,由于资源非空,则不产生,也进入等待状态;然后t3线程获得cpu执行权,输出一次,将资源置为空,唤醒等待线程池中的头一个线程t1,t1从进入等待的那个地方醒过来,不再判断if条件,继续向下执行,输入一次数据,将资源置为非空,唤醒等待线程池中的头一个线程t2,t2也从进入等待的那个地方醒过来,不再判断if条件,继续向下执行,输入一次数据,注意这次将醒来的t1线程输入的数据覆盖掉了,导致漏打。所以进程醒来还需在判断if条件。所以将if改成while。但是如果这样,按照之前的唤醒机制,每次只唤醒一个,最后可能会导致所有进程都进入了等待状态。(冻结状态也称之为中断状态)因此还应该将notify()改成notifyAll()。

代码如下:

class Res

{

String name;

String sex;

private boolean flag=false;

 

public synchronized void set(String name,String sex)

{

while(flag)

try{this.wait();}catch(Exception e){}

this.name=name;

this.sex=sex;

flag=true;

this.notifyAll();

}

 

public synchronized void out()

{

while(!r.flag)

try{r.wait();}catch(Exception e){}

System.out.println(r.name+”...”+r.sex);

flag=false;

this.notifyAll();

 

}

}

class Input implements Runnable

{

private Res r;

Input(Res r)

{

this.r=r;

}

public void run()

{

int x=0;

while(true)

{

 

if(x==0)

{

r.set(“mike”,”man”);

}

else

{

r.set(“丽丽”,”女女”);

}

x=(x+1)%2;

}

}

}

}

class Output implements Runnable

{

private Res r;

Output(Res r)

{

this.r=r;

}

public void run()

{

while(true)

{

r.out();

      }

}

}

}

class InputOutputDemo

{

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(in);

 

Thread t3=new Thread(out);

Thread t4=new Thread(out);

 

t1.start();

t2.start();

t3.start();

t4.start();

 

}

}

线程间通信jdk5.0升级版:

上述中notifyAll()不仅唤醒了对方进程,也唤醒了本方进程。现在我们想只唤醒对方线程,升级版做到了这一点,依据:不可以对不同锁中的线程进行唤醒。

升级后:没有了synchronized,和wait()、notifyAll()、notify()。

有了Lock、Condition两个类。

Lock中有:lock()、unlock()

Condition中有:await()、signal()

 

代码如下:

class Res

{

String name;

String sex;

private boolean flag=false;

private Lock lock=new ReentrantLock();

private Condition condition-in=lock.newCondition();

private Condition condition-out=lock.newCondition();

 

 

public  void set(String name,String sex)

throws InterruptedException

{

Lock.lock();

Try

{

while(flag)

condition-in.await();

this.name=name;

this.sex=sex;

flag=true;

condition-out.signal();

}

Finally

{

Lock.unlock();

}

}

 

public void out()

throws InterruptedException

{

Lock.lock();

try

{

while(!r.flag)

condition-out.await();

System.out.println(r.name+”...”+r.sex);

flag=false;

condition-in.signal();

}

finally

{

Lock.unlock();

}

 

}

}

class Input implements Runnable

{

private Res r;

Input(Res r)

{

this.r=r;

}

public void run()

{

int x=0;

while(true)

{

try

{

if(x==0)

{

r.set(“mike”,”man”);

}

else

{

r.set(“丽丽”,”女女”);

}

x=(x+1)%2;

}

catch(Exception e)

{

}

}

}

}

class Output implements Runnable

{

private Res r;

Output(Res r)

{

this.r=r;

}

public void run()

{

while(true)

{

try

{

r.out();

}

catch(Exception e)

{

}

}

}

}

class InputOutputDemo

{

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(in);

 

Thread t3=new Thread(out);

Thread t4=new Thread(out);

 

t1.start();

t2.start();

t3.start();

}

}

 

6、线程中常用方法

(1)停止线程

① stop(),但已经过时。

② 让run()结束。run()中有循环代码,通过一个标记控制循环条件。特殊情况:当线程处于冻结状态,就读不到标记,线程就不能结束,停在那,此时用Thread类中interrupt()方法,通过线程对象调用,让线程活过来,那么就可以读到标记,让run()结束,即让线程结束。

 

 

(2)守护线程

守护线程:后台线程。

开启后和前台线程共同抢夺cpu执行权,运行、开启都没有区别,就结束有区别。当所有的前台线程都结束后,后台线程自动结束。将线程设置为守护线程:在开启之前,线程对象.setDaemon(true);即可。这样可用在停止线程中。(可与解决:结束run()的第二种方式中所遇到的特殊情况)

 

(3)join方法

Public final void join()throws InterruptedException

{

 

}

当A线程执行到了B线程的.join()时,A就会等待,等B线程都执行完,A才会执行(受这种影响的只有A)。Join可以用来临时加入线程执行。

 

(4)优先级

抢cpu资源的频率高低。

set priority(int newPriority):更改线程的优先级。所有线程的默认优先级是5.

 

如:

static final int MAX_PRIORITY:线程可以具有的最高优先级:10;

static final int MiN_PRIORITY:线程可以具有的最小优先级:1;

static final int NORM_PRIORITY:线程可以具有的默认优先级:5;

 

(5)toString()

Thread 类中的toString()返回该线程的字符串表示形式,包括线程名称,优先级和线程组(谁开启的你,就属于哪一个组)。

 

(6)yield

Static void yield();暂停当前正在执行的线程对象,并执行其他线程。被暂停的线程还是活的,还可以抢夺cpu执行权。

 

 

7、总结

什么时候用多线程?

当某些代码需要“同时”被执行时,就用单独的线程进行封装。你可以封装成三个类,也可以有更简单的写法。

class ThreadTest

{

public static void main(String[] args)

{

for(int x=0;x<100;x++)

{

System.out.println(

Thread.currentThread().getName()+”....”+x

);

}

for(int x=0;x<100;x++)

{

System.out.println(

Thread.currentThread().getName()+”....”+x

);

}

for(int x=0;x<100;x++)

{

System.out.println(

Thread.currentThread().getName()+”....”+x

);

}

}

}

利用多线程:

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

);

}

}

}.star();

for(int x=0;x<100;x++)

{

System.out.println(

Thread.currentThread().getName()+”....”+x

);

}

Runnble r=new Runnable()

{

public void run()

{

for(int x=0;x<100;x++)

{

System.out.println(

Thread.currentThread().getName()+”....”+x

);

}

}

};

new Thread(r).star();

}

}

 

2015-12-01至 2015-12-05著
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: