------- android培训、java培训、期待与您交流! ----------
多线程
1 创建线程
java中Thread类用于描述线程
【】创建线程方法一
1、定义类继承Thread
Thread中run()方法用于存储线程要运行的代码
2、复写Thread中的run方法
将自定义的代码存储在run方法中
3、调用线程的start方法(启用线程,调用run方法)【线程都有自己的名称,Thread-编号,编号从0开始】
class MyThread extends Thread //定义类继承Thread
{
public void run()//覆写Thread中的run方法
{
for (int i = 0; i < 450; i++)
{
System.out.println("Thread线程走起。。。"+i);
}
}
}
class Test{
public static void main(String[] args){
MyThread a=new MyThread();//创建线程
a.start();//启动线程,并执行run方法
for (int x = 0; x < 350; x++) {
System.out.println("hallo java"+x);
}
}
}
P.S.
线程的几种运行状态:
P.S.
currentThread():获取当前线程对象,可以用this代替
get name():获取线程名称
setName 或 构造函数:设置线程名称:
class MyThread extends Thread //定义类继承Thread
{
MyThread(String s){
super(s);
}
public void run()//覆写Thread中的run方法
{
for (int i = 0; i < 3; i++)
{
System.out.println((Thread.currentThread()==this)+"..."+this.getName()+"Thread线程走起。。。"+i);//getName()获取线程名称
}
}
}
class Test{
public static void main(String[] args){
MyThread a=new MyThread("a---");//创建线程
MyThread b=new MyThread("b---");
//a.setName("MY---");//设置线程名称
a.start();//启动线程,并执行run方法
b.start();
//a.run();//一般对象调用,没有启动线程
for (int x = 0; x < 3; x++) {
System.out.println("hallo java"+x);
}
}
}
运行结果:
【】创建线程方法二
1、定义类实现Runnable接口
2、覆盖Runnable中的run方法
3、通过Thread类建立线程对象
4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
Thread(Runnable target),将子类Runnable对象传递给Thread类的构造函数,
自定义的run方法所属的是Runnable接口的子类对象,
5、调用Thread类的start方法开启线程并调用Runnable接口类的run方法
class Ticket implements Runnable //定义类继承Thread
{
int num=10;
public void run()//覆写Thread中的run方法
{
while(true){
if (num>0)
System.out.println(Thread.currentThread().getName()+"售票"+num--);
}
}
}
class Test{
public static void main(String[] args){
Ticket t=new Ticket();
Thread a1=new Thread(t);
Thread a2=new Thread(t);
Thread a3=new Thread(t);
a1.start();//启动线程,并执行run方法\
a2.start();
a3.start();
}
}
P.S.
第二种创建多线程的方式避免因java单继承的局限性
2 线程的安全问题
多线程的安全隐患:多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,另一个线程参与进来
class Ticket implements Runnable //定义类继承Thread
{
int num=100;
public void run()//覆写Thread中的run方法
{
while(true){
if (num>0){
try {
Thread.sleep(10);//线程休眠10ms,会抛出异常,祥见api thread类
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售票"+num--);
}}
}
}
class Test{
public static void main(String[] args){
Ticket t=new Ticket();
Thread a1=new Thread(t);
Thread a2=new Thread(t);
Thread a3=new Thread(t);
a1.start();//启动线程,并执行run方法\
a2.start();
a3.start();
}
}
运行结果
p.s.
public static void sleep(long millis,int nanos) throws InterruptedException 在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行)
多线程安全问题原因:
1. 多个线程在操作共享的数据。
2. 操作共享数据的线程代码有多条。当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
多线程安全解决法:
对多条操作共享数据语句,只能让一个线程都执行完,过程中,其他线程无法参与
解决方法一:【同步代码块】格式:synchronized{需要同步的代码块}-类似火车卫生间上锁
【】同步的前提:
1、必须有两个以上的线程
2、必须多个线程拥有同一个锁【静态同步函数的锁是class对象即类名.Class-字解码对象文件】
class Ticket implements Runnable //定义类继承Thread
{
int num=500;
Object obj=new Object();
public void run()//覆写Thread中的run方法
{
while(true){
synchronized(obj) {//同步代码块,锁是obj
if (num>0){
try {
Thread.sleep(10);//线程休眠10ms,会抛出异常,祥见api thread类
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售票"+num--);
}}}
}
}
class Test{
public static void main(String[] args){
Ticket t=new Ticket();
Thread a1=new Thread(t);
Thread a2=new Thread(t);
Thread a3=new Thread(t);
a1.start();//启动线程,并执行run方法\
a2.start();
a3.start();
}
}
运行结果:
P.S.
原因在于Object对象相当于是一把锁,只有抢到锁的线程,才能进入同步代码块向下执行。因此,当num=1时,CPU切换到某个线程后,如上图的Thread-3线程,其他线程将无法通过同步代码块继而进行if判断语句,只有等到Thread-3线程执行完“num--”操作(此后num的值为0),并且跳出同步代码块后,才能抢到锁。其他线程即使抢到锁,然而,此时num值已为0,也就无法通过if语句判断,从而无法再执行“num--”的操作了,也就不会出现0、-1、-2等情况了。
解决方法二:【同步函数】:把同步作为修饰符放函数上,格式: public synchronized void add(int n)【同步函数的锁是this】
【例】
class Bank{
private int sum ;
public synchronized void add(int num)//同步函数,锁为this
{
sum = sum + num;
System. out.println("sum = " + sum);
}
}
class Cus implements Runnable{
private Bank b = new Bank();
public void run(){
for(int x = 0; x < 8; x++){
b.add(100);//存100
}
}
}
class Test{
public static void main(String[] args){
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
运行结果:
1. 同步函数的锁是固定的this。
2. 同步代码块的锁是任意的对象。
建议使用同步代码块。
由于同步函数的锁是固定的this,同步代码块的锁是任意的对象,那么如果同步函数和同步代码块都使用this作为锁,就可以实现同步。
class Ticket implements Runnable{
private int num = 100;
boolean flag = true;
public void run(){
if(flag ){
while(true ){
synchronized(this ){//同步代码块,将同步代码块的锁使用this,可以实现同步代码快和同步函数的同步
if(num > 0){
try{
Thread. sleep(10);
} catch(InterruptedException e){
e.printStackTrace();
}
System. out.println(Thread.currentThread().getName() + "...obj..." + num--);
}
}
}
} else
while(true )
show();
}
public synchronized void show(){//同步函数
if(num > 0){
try{
Thread. sleep(10);
} catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "...function..." + num--);
}
}
}
class Test{
public static void main(String[] args){
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{//如果不加,线程t1尚未真正启动,flag已经设置为false,那么当t1执行的时候,就会按照flag为false的情况执行,线程t2也按照flag为false的情况执行,实验就达不到目的了。
Thread. sleep(10);
} catch(InterruptedException e){
e.printStackTrace();
}
t. flag = false ;
t2.start();
}
}
p.s.
静态的同步函数使用的锁不是this(静态方法中不可能存在this)是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示。
class Ticket implements Runnable{
private static int num = 100;
boolean flag = true;
public void run(){
if(flag ){
while(true ){
synchronized(Ticket.class){//锁为Ticket的字节码文件对象,和下面的同步函数锁一样
if(num > 0){
try{
Thread. sleep(10);
} catch(InterruptedException e){
e.printStackTrace();
}
System. out.println(Thread.currentThread().getName() + "...obj..." + num--);
}
}
}
} else
while(true )
show();
}
public static synchronized void show(){//静态函数的锁是所属的字节码文件对象
if(num > 0){
try{
Thread. sleep(10);
} catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "...function..." + num--);
}
}
}
class Test{
public static void main(String[] args){
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{//下面这条语句一定要执行。因为可能线程t1尚未真正启动,flag已经设置为false,那么当t1执行的时候,就会按照flag为false的情况执行,线程t2也按照flag为false的情况执行,实验就达不到目的了。
Thread. sleep(10);
} catch(InterruptedException e){
e.printStackTrace();
}
t. flag = false ;
t2.start();
}
}
【死锁】:锁的同步嵌套
class Die implements Runnable
{
private Boolean flag;
Die(Boolean flag)
{
this.flag=flag;
}
public void run()
{
if(flag)
{
synchronized (Lock.locka)
{
System.out.println("Ture...LOCK...A");
synchronized (Lock.lockb)
{
System.out.println("Ture...LOCK...B");//线程一拿到A锁,阻塞在此,需要B锁
}
}
}
else
{
synchronized (Lock.lockb)
{
System.out.println("FALSE...LOCK...B");
synchronized (Lock.locka)
{
System.out.println("FALSE...LOCK...A");//线程二拿到B锁,阻塞在此,需要A锁
}
}
}
}
}
class Lock
{
static Object locka=new Object();
static Object lockb=new Object();
}
class Test{
public static void main(String[] args){
Thread a1=new Thread(new Die(true));
Thread a2=new Thread(new Die(false));
a1.start();
a2.start();
}
}
运行结果:线程间的通信:
原因:线程1拿了A锁需要B锁,线程二拿了B锁需要A锁
【生产消费问题-单生产、单消费】
多个线程在处理统一资源,但是操作动作却不同(例如:一个存一个取),这时候就需要线程间通信。
class Person{
String name,sex;
}
class Input implements Runnable{
int i=0;
Person pi=new Person();
Input(Person p){
pi=p;
}
public void run(){
while(true)
{
if(i==1){//间隔存放标志
pi.name="麻子";
pi.sex="男...男...男...";}
if(i==0)
{
pi.name="Ada";
pi.sex="women...women...women...";
}
i=(i+1)%2;//将标志更改
}
}
}
class Output implements Runnable{
Person po=new Person();
Output(Person p){
po=p;
};
public void run(){
while (true)
{
System.out.println(po.name+"---"+po.sex);
}
}
}
public class Test {
public static void main(String[] args) {
Person p=new Person();
Input i=new Input(p);
Output o=new Output(p);
Thread a1=new Thread(i);
Thread a2=new Thread(o);
a1.start();
a2.start();
}
}
运行结果:
错误原因:输入输出线程不同步
class Person{
String name,sex;
}
class Input implements Runnable{
int i=0;
Person pi=new Person();
Input(Person p){
pi=p;
}
public void run(){
while(true)
{
synchronized (Input.class) {//将输入输出线程同步,并定义同一个锁
if(i==1){
pi.name="麻子";
pi.sex="男...男...男...";}
if(i==0)
{
pi.name="Ada";
pi.sex="women...women...women...";
}
i=(i+1)%2;
}
}
}
}
class Output implements Runnable{
Person po=new Person();
Output(Person p){
po=p;
};
public void run(){
while (true)
{
synchronized (Input.class) {//将输入输出线程同步,并定义同一个锁
System.out.println(po.name+"---"+po.sex);
}
}
}
}
public class Test {
public static void main(String[] args) {
Person p=new Person();
Input i=new Input(p);
Output o=new Output(p);
Thread a1=new Thread(i);
Thread a2=new Thread(o);
a1.start();
a2.start();
}
}
如何实现,存一个取一个?
应用【等待唤醒机制】
wait()等待
notify()唤醒
存完后等待,直到唤醒,取完后唤醒
P.S.
wait、notify、notifyAll都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,只有同步才有锁
这些方法在操作同步线程时,都必须标识他们所操作的线程
notify只能唤醒同一个监视器上的等待线程
又监视器是任意对象,因此这些方法要被定义在object中
class Person{
String name,sex;
Boolean flag=false;//资源中的标记
}
class Input implements Runnable{
int i=0;
private Person p;
Input(Person p){
this.p=p;
}
public void run(){
while(true)
{
synchronized (p) {
if(p.flag)
try {
p.wait();//wait会抛出异常,需要标识所操作的线程,用锁标识,锁.wait()
} catch (Exception e) {
}
if(i==1){
p.name="麻子";
p.sex="男...男...男...";}
if(i==0)
{
p.name="Ada";
p.sex="women...women...women...";
}
i=(i+1)%2;
p.flag=true;
p.notify();//需要标识所操作的线程
}
}
}
}
class Output implements Runnable{
private Person p;
Output(Person p){
this.p=p;
};
public void run(){
while (true)
{
synchronized (p) {
if(!p.flag)
try {
p.wait();//需要标识所操作的线程
} catch (Exception e) {
}
else
{
System.out.println(p.name+"---"+p.sex);
p.flag=false;
p.notify();//需要标识所操作的线程
}
}
}
}
}
public class Test {
public static void main(String[] args) {
Person p=new Person();
Input i=new Input(p);
Output o=new Output(p);
Thread a1=new Thread(i);
Thread a2=new Thread(o);
a1.start();
a2.start();
}
}
运行结果
class Resource{
private String name;
private int num=1;
private Boolean flag=false;
public synchronized void set(String name){
if(flag){
try {
wait();
} catch (Exception e) {
// TODO: handle exception
}
}
this.name=name+"....."+num++;
System.out.println(Thread.currentThread().getName()+"生产。。。"+this.name);
flag=true;
notify();
}
public synchronized void out(){
if(!flag){
try {
wait();
} catch (Exception e) {
// TODO: handle exception
}
}
System.out.println(Thread.currentThread().getName()+"消费。。。。。。"+this.name);
flag=false;
notify();
}
}
class Input implements Runnable{
private Resource r;
Input(Resource r){
this.r=r;
}
public void run(){
while(true){
r.set("apple");
}
}
}
class Output implements Runnable{
private Resource r;
Output(Resource r){
this.r=r;
}
public void run(){
while(true){
r.out();;
}
}
}
public class Test {
public static void main(String[] args) {
Resource r=new Resource();
Input in=new Input(r);
Input in1=new Input(r);
Output ot=new Output(r);
Output ot1=new Output(r);
Thread a1=new Thread(in);
Thread a2=new Thread(in1);
Thread a3=new Thread(ot);
Thread a4=new Thread(ot1);
a1.start();
a2.start();
a3.start();
a4.start();
}
}
运行结果:
按照单消费的思路,会出现安全隐患,生产一次,被消费了两次,解决方法:判断资源标记改while()循环,用notifyAll()唤醒所有等待线程
class Resource{
private String name;
private int num=1;
private Boolean flag=false;
public synchronized void set(String name){
while(flag){//改if判断循环为while循环,让每次唤醒后都判断资源标记
try {
wait();//线程等待
} catch (Exception e) {
// TODO: handle exception
}
}
this.name=name+"....."+num++;
System.out.println(Thread.currentThread().getName()+"生产。。。"+this.name);
flag=true;
notifyAll();//改notify为notifyAll(),防止程序卡死
}
public synchronized void out(){
if(!flag){
try {
wait();
} catch (Exception e) {
// TODO: handle exception
}
}
System.out.println(Thread.currentThread().getName()+"消费。。。。。。"+this.name);
flag=false;
notify();
}
}
class Input implements Runnable{
private Resource r;
Input(Resource r){
this.r=r;
}
public void run(){
while(true){
r.set("apple");
}
}
}
class Output implements Runnable{
private Resource r;
Output(Resource r){
this.r=r;
}
public void run(){
while(true){
r.out();;
}
}
}
public class Test {
public static void main(String[] args) {
Resource r=new Resource();
Input in=new Input(r);
Input in1=new Input(r);
Output ot=new Output(r);
Output ot1=new Output(r);
Thread a1=new Thread(in);
Thread a2=new Thread(in1);
Thread a3=new Thread(ot);
Thread a4=new Thread(ot1);
a1.start();
a2.start();
a3.start();
a4.start();
}
}
运行结果:
JDK1.5 新特性:
将同步和锁封装成对象
1、将隐式同步synchronized替换成显式的lock操作
Lock接口:出现替代了同步代码块或者同步函数,将同步的隐式操作变成显示锁操作。同时更为灵活,可以一个锁上加上多组监视器
1、lock():获取锁。
2、unlock():释放锁---为了防止异常出现,导致锁无法被关闭,所以锁的关闭动作要放在finally中。
2、将object中的wait、notify、notifyAll替换为Condition对象,将这些监视器方法单独进行了封装,变成Condition监视器对象,可以与任意锁进行组合
import java.util.concurrent.locks.*;
import java.util.concurrent.locks.*;
class Resource{
private String name;
private int num=1;
private Boolean flag=true;
Lock lock=new ReentrantLock();//创建一个锁对象
Condition con1=lock.newCondition();//通过已有的锁获取该锁对应的监视器对象
Condition con2=lock.newCondition();
// synchronized void set(String name)
public void set(String name)throws Exception
{
lock.lock();//锁
try{
while(flag)
{
con1.await();//con1监视器等待
}
this.name=name+"....."+num++;
System.out.println(Thread.currentThread().getName()+"生产。。。"+this.name);
flag=true;
con2.signal();//唤醒con2
}
finally{
lock.unlock();//解锁
}
}
public void out()throws Exception
{
lock.lock();//锁
try {
while(!flag){
con2.await();//con2等待
}
System.out.println(Thread.currentThread().getName()+"消费。。。。。。"+this.name);
flag=false;
con1.signal();//唤醒con1
}
finally{
lock.unlock();//解锁
}
}
}
class Input implements Runnable{
private Resource r;
Input(Resource r){
this.r=r;
}
public void run(){
while(true){
try {
r.set("apple");
} catch (Exception e) {
// TODO: handle exception
}
}
}
}
class Output implements Runnable{
private Resource r;
Output(Resource r){
this.r=r;
}
public void run(){
while(true){
try {
r.out();
} catch (Exception e) {
// TODO: handle exception
}
}
}
}
public class Test {
public static void main(String[] args) {
Resource r=new Resource();
Input in=new Input(r);
Input in1=new Input(r);
Output ot=new Output(r);
Output ot1=new Output(r);
Thread a1=new Thread(in);
Thread a2=new Thread(in1);
Thread a3=new Thread(ot);
Thread a4=new Thread(ot1);
a1.start();
a2.start();
a3.start();
a4.start();
}
}
运行结果
怎么控制线程的任务结束呢? 任务中都会有循环结构,只要控制住循环就可以结束任务。
总结
1、多线成可以解决不同任务同时进行的问题,解决很多现实生活中例如多窗口售票的问题,但是当多线程操作共有数据时容易产生安全隐患,这时候就需要应用同步的知识来解决安全隐患,这个是需要特别注意的点。
2、创建线程的两种方法:extends Thread、implents Runnable,两者原理上想通,最终都要创建Thread的子类或者类(实现接口的类作为参数传递)开启线程,第二种方法避免了单继承的局限性,更为常用