------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
/**
* 需求:创建多线程和主函数交替执行。
* 思路:继承Thread类,将要执行的代码存储进run方法
* @author jinlong
* */
package com.blog.part3.多线程;
//创建线程Test
class Test extends Thread
{
private String name;
//构造函数
Test(String name)
{
this.name=name;
}
//复写run方法,将要执行多线程的代码写进去
public void run()
{
for(int x=0;x<60;x++)
//这里说一些Thread.currentThread().getName()方法是为了获取正在运行的进程的名字
//每个进程在运行时都有默认名字,当然也可以人为赋予名字
System.out.println(this.name+".....正运行....."+x);
}
}
public class ThreadDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创键两个线程
Test a=new Test("线程一");
Test b=new Test("线程二");
//启动这两个线程
a.start();
b.start();
//设置下主线程执行的代码
for(int x=0;x<200;x++)
System.out.println("主线程运行ing");
}
}
运行结果:
/**
* 需求:用实现Runnble方式创建多线程,解决多窗口卖票问题。
* 思路:1.创建类实现Runnable接口,把代码写入run方法。
* 2.创建实现接口的子类实例对象。
* 3.将对象作为构造函数参数,创建四条不同线程并且启动。
* @author jinlong
* */
package com.blog.part3.多线程;
//创建Ticket类实现Runnable接口
class Ticket implements Runnable
{
//定义变量,表示票剩余量
private int tick=500;
@Override
//重写run方法
public void run()
{
while(true)
{
if(tick>0)
{
//显示当前线程以及剩余票
System.out.println(Thread.currentThread().getName()+"正在卖第"+tick--+"张票");
}
}
}
}
public class ThreadDemo1
{
public static void main(String[] args)
{
//创建对象
Ticket t=new Ticket();
//有多个窗口在同时卖票,创建多个Thread对象表示
//这里每个Thread对象就是一个Thread线程。
Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);
//启动这四个线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
为什么要将Runnable接口的子类对象传递给Thread的构造函数?
因为,自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去指定对象的run方法,就必须明确该run方法所属对象。
创建步骤:
a、定义类实现Runnable的接口。
b、覆盖Runnable接口中的run方法。目的也是为了将线程要运行的代码存放在该run方法中。
c、通过Thread类创建线程对象。
d、将Runnable接口的子类对象作为实参传递给Thread类的构造方法。
e、调用Thread类中start方法启动线程。start方法会自动调用Runnable接口子类的run方法。
实现方式好处:避免了单继承的局限性。在定义线程时,建议使用实现方式。
被创建:等待启动,调用start启动。
运行状态:具有执行资格和执行权。
临时状态(阻塞):有执行资格,但是没有执行权。
冻结状态:遇到sleep( )方法和wait( )方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法唤醒时时,获得执行资格,变为 临时状态。
消忙状态:stop( )方法,或者run方法结束。
图解:
if(tick>0)
{
//显示当前线程以及剩余票
System.out.println(Thread.currentThread().getName()+"正在卖第"+tick-- +"张票");
}
原理是当Thread-2卖到最后一张票,执行到System打印语句时,但没执行tick--时,此时tick值为1,
CPU切换到Thread-1、Thread-2、Thread-3之后,这些线程依然可以通过if判断,从而执行“tick--”的操作,因而出现了0、-1、-2的情况。
用法:
synchronized(对象)
{需要被同步的代码}
同步可以解决安全问题的根本原因就在那个对象上。其中对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
示例:
/*
给卖票程序示例加上同步代码块。
*/
class Ticket implements Runnable
{
private int tick=100;
Object obj = new Object();
public void run()
{
while(true)
{
//给程序加同步,即锁
synchronized(obj)
{
if(tick>0)
{
try
{
//使用线程中的sleep方法,模拟线程出现的安全问题
//因为sleep方法有异常声明,所以这里要对其进行处理
Thread.sleep(10);
}
catch (Exception e)
{
}
//显示线程名及余票数
System.out.println(Thread.currentThread().getName()+"..tick="+tick--);
}
}
}
}
}
格式:
在函数上加上synchronized修饰符即可。
那么同步函数用的是哪一个锁呢?
函数需要被对象调用。那么函数都有一个所属对象引用。就是this。所以同步函数使用的锁是this。
示例:
class Ticket implements Runnable
{
private int tick=100;
Object obj = new Object();
public void run()
{
while(true)
{
show();
}
}
//直接在函数上用synchronized修饰即可实现同步
public synchronized void show()
{
if(tick>0)
{
try
{
//使用线程中的sleep方法,模拟线程出现的安全问题
//因为sleep方法有异常声明,所以这里要对其进行处理
Thread.sleep(10);
}
catch (Exception e)
{
}
//显示线程名及余票数
System.out.println(Thread.currentThread().getName()+"..tick="+tick--);
}
}
}
1、同步的前提
a,必须要有两个或者两个以上的线程。
b,必须是多个线程使用同一个锁。
2、同步的利弊
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源。
3、如何寻找多线程中的安全问题
a,明确哪些代码是多线程运行代码。
b,明确共享数据。
c,明确多线程运行代码中哪些语句是操作共享数据的。
类名.class 该对象的类型是Class或者 this.getClass
这就是静态函数所使用的锁。而静态的同步方法,使用的锁是该方法所在类的字节码文件对象。类名.class
/*
加同步的单例设计模式————懒汉式
*/
class Single
{
private static Single s = null;
private Single(){}
//函数被静态修饰了
public static void getInstance()
{
if(s==null)
{
//使用锁的对象其实是Single.class
synchronized(Single.class)
{
if(s==null)
s = new Single();
}
}
return s;
}
}
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)
s = new Single();
}
}
return s ;
}
}
/**
* 需求:设计演示死锁
* 思路:利用标记更改判断条件,设计嵌套形成死锁
* @author jinlong
* */
package com.blog.part3.多线程;
class Ticket implements Runnable
{
private static int num = 100;
Object obj = new Object();
//设计标记flag控制状态
boolean flag = true;
public void run()
{
if(flag )
{
while(true )
{
//函数加锁
synchronized(obj )
{
show();
}
}
} else
while(true )
show();
}
//函数上加锁
public synchronized void show()
{
synchronized(obj )
{
if(num > 0)
{
//异常处理
try{
//休眠200毫秒
Thread. sleep(200);
} catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "...function..." + num--);
}
}
}
}
class DeadLockDemo
{
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(200);
} catch(InterruptedException e){
e.printStackTrace();
}
//改变标记状态
t. flag = false ;
t2.start();
}
}
/**
* 需求:演示死锁
* 思路:设计两个对象,每一个线程都要经过两个对象锁,但是两个对象锁的执行顺序不同。
* 预期中目标是:线程1加载了a锁正要用b锁,于此同时线程2加载了b锁正要用a锁。
* 这样a,b两个对象锁都有人使用同时又都需要被使用。形成死锁
* @author jinlong
* */
package com.blog.part3.多线程;
//定义Test类
class Test implements Runnable{
//私有函数控制Test状态
private boolean flag ;
//根据传入值初始化flag
Test( boolean flag){
this.flag = flag;
}
public void run()
{
//根据flag值,将两条线程分开
if(flag ){
while(true )
//这里先执行locka对象锁
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 )
//这里先执行lockb对象锁
synchronized(MyLock.lockb){
System.out.println(Thread.currentThread().getName() + "...else lockb...");
synchronized(MyLock.locka){
System.out.println(Thread.currentThread().getName() + "...else locka...");
}
}
}
}
}
class MyLock{
//设计两个对象locka和lockb
public static final Object locka = new Object();
public static final Object lockb = new Object();
}
class DeadLockDemo1{
public static void main(String[] args){
//根据传入参数,赋予ab两个线程不同路线。
Test a = new Test(true );
Test b = new Test(false );
Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
t1.start();
t2.start();
}
}
运行结果:
/**
* 需求:间隔打印mike和lili的信息,
* 思路:设计两个线程,控制这两个线程有规律的交替执行,
* 同时要保证每一条数据的完整性。不能出现名字性别不对应情况。
*@author jinlong
* */
package com.blog.part3.多线程;
//定义Person类
class Person{
//定义Person两个基本属性,name和sex
private String name ;
private String sex ;
//定义标记
private boolean flag = false;
//set方法用来修改Person属性信息
public synchronized void set(String name,String sex)
{
if(flag )
try{
//冻结当前线程进入
this.wait();
} catch(InterruptedException e){
e.printStackTrace();
}
this.name = name;
this.sex = sex;
flag = true ;
//唤醒其他线程
this.notify();
}
//out方法用来打印输出name和sex
public synchronized void out()
{
if(!flag )
try{
this.wait();
} catch(InterruptedException e){
e.printStackTrace();
}
System. out.println(name + "..." + sex);
flag = false ;
this.notify();
}
}
//输入类,实现Runnable,保证对Person属性的修改能同步
class Input implements Runnable
{
Person r;
Input(Person r){
this.r = r;
}
public void run(){
int x = 0;
while(true ){
if(x == 0){
r.set( "mike","男" );
} else{
r.set( "lili","女" );
}
//实现交替输入mike和lili
x = (x + 1)%2;
}
}
}
//定义输出类,同样保证输出信息的对应
class Output implements Runnable{
Person r;
Output(Person r){
this.r = r;
}
public void run(){
while(true ){
r.out();
}
}
}
class PersonDemo {
public static void main(String[] args){
//创建资源
Person r = new Person();
//创建任务
Input in = new Input(r);
Output out = new Output(r);
//创建线程,执行路径
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
//开启线程
t1.start();
t2.start();
}
}
运行结果:
/**
* 需求:演示生产消费经典案例
* 思路:首先要保证生产和消费的同步执行。防止产生死锁。
* */
package com.blog.part3.多线程;
//定义Res类代表产品
class Res
{
//定义私有属性name代表产品名字
private String name;
//变量count代表产品当前数量
private int count;
private boolean flag=false;
//生产方法,即生产产品,函数同步
public synchronized void shengChan(String name)
{
while(flag)
try
{
wait();
}
catch (Exception e)
{
}
//产品名为:传入参数+产品ID+当前数量
this.name = name +"产品ID"+count++;
//打印当前线程名,以及当前线程生产的产品名字
System.out.println(Thread.currentThread().getName()+"......生产者++++"+this.name);
flag = true;
//唤醒其他线程
notifyAll();
}
//定义xiaofei方法,同样函数同步
public synchronized void xiaoFei()
{
while(!flag)
try
{
wait();
}
catch (Exception e)
{
}
//打印当前线程作为消费者
System.out.println(Thread.currentThread().getName()+"......消费者"+this.name);
flag=false;
//唤醒其他线程
notifyAll();
}
}
//定义消费者类,用来增加产品量
class Produce implements Runnable
{
//私有成员变量Res类型的r
private Res r;
//通过构造函数给r赋值
Produce(Res r)
{
this.r=r;
}
public void run()
{
while(true)
{
//将生产者的生产动作写如run方法
r.shengChan("++制造商品++");
}
}
}
//定义消费者
class Consumer implements Runnable
{
//私有成员 r
private Res r;
//通过构造函数给r赋值
Consumer(Res r)
{
this.r=r;
}
public void run()
{
while(true)
{
//消费者行为
r.xiaoFei();
}
}
}
class ProConDemo
{
public static void main(String[] args)
{
//创建Res的唯一对象r
Res r= new Res();
//创建生产者对象p和消费者对象c,并且他们初始化参数值都为r
Produce p= new Produce(r);
Consumer c= new Consumer(r);
//给生产者定义两个线程,即有两个生产者
Thread t1= new Thread (p);
Thread t2= new Thread (p);
//给消费者定义两个线程,即两个消费者。
Thread t3= new Thread (c);
Thread t4= new Thread (c);
//启动线程。
t1.start();
t2.start();
t3.start();
t4.start();
}
}
运行结果:
/**
* 需求:1.生产者生产商品,供消费者使用 有两个或者多个生产者,生产一次就等待消费一次
有两个或者多个消费者,等待生产者生产一次就消费掉 。
2.使用Lock、Condition
思路:定义资源类,存取方法通过Lock实现同步
定义生产者和消费者,各自实现Runnable接口,实现对资源类的同步操作
* */
package com.blog.part3.多线程;
//使用Lock需要导入包
import java.util.concurrent.locks.*;
//定义产品资源类
class Resource
{
//定义产品属性变量name和count
private String name;
private int count=1;
//标记
private boolean flag = false;
//多态
private Lock lock=new ReentrantLock();
//创建两Condition对象,分别来控制等待或唤醒本方和对方线程
Condition condition_pro=lock.newCondition();
Condition condition_con=lock.newCondition();
//定义生产者使用的方法,产品增加
//p1、p2共享此方法
public void setProducer(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();//解锁,这个动作一定执行
}
}
//c1、c2共享此方法
public void getConsumer()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();
}
}
}
//生产者线程
class Producer implements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res=res;
}
//复写run方法
public void run()
{
while(true)
{
try
{
res.setProducer("商品");
}
catch (InterruptedException e)
{
}
}
}
}
//消费者线程
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res=res;
}
//复写run
public void run()
{
while(true)
{
try
{
res.getConsumer();
}
catch (InterruptedException e)
{
}
}
}
}
class ProConDemo1
{
public static void main(String[] args)
{
Resource res=new Resource();
new Thread(new Producer(res)).start();//第一个生产线程 p1
new Thread(new Consumer(res)).start();//第一个消费线程 c1
new Thread(new Producer(res)).start();//第二个生产线程 p2
new Thread(new Consumer(res)).start();//第二个消费线程 c2
}
}
运行结果:
在JDK 1.5版本之前,有stop停止线程的方法,但升级之后,此方法已经过时。
如果停止线程,只有一种办法,那就是让run方法结束。
1、开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。
如:run方法中有如下代码,设置一个flag标记。那么只要在主函数或者其他线程中,在该线程执行一段时间后,将标记flag赋值false,该run方法就会结束,线程也就停止了。
public void run()
{
while(flag)
{
System.out.println(Thread.currentThread().getName()+"....run");
}
}
2、
上面的
1
方法可以解决一般情况,但是有一种特殊情况:就是当线程处于冻结状态。就不会读取到标记。那么线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。Thread类提供该方法interrupt();
package com.blog.part3.多线程;
class StopThread implements Runnable
{
private boolean flag =true;
public void run()
{
while(flag)
{
System.out.println(Thread.currentThread().getName()+"....run");
}
}
public void changeFlag()
{
flag = false;
}
}
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)
{
t1.interrupt();//清除冻结状态
t2.interrupt();
st.changeFlag();//改变循环标记
break;
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
System.out.println("over");
}
}
概念/目的:后台线程主要是为其他线程(相对可以称之为前台线程)提供服务,或“守护线程”。如JVM中的垃圾回收线程。
生命周期:后台线程的生命周期与前台线程生命周期有一定关联。主要体现在:当所有的前台线程都进入死亡状态时,后台线程会自动死亡(其实这 个也很好理解,因为后台线程存在的目的在于为前台线程服务的,既然所有的前台线程都死亡了,那它自己还留着有什么用)。
设置后台线程:调用Thread对象的setDaemon(true)方法可以将指定的线程设置为后台线程。
/**
* 需求:演示守护线程
* 思路:1.设计一个永真死循环,不打扰情况下不断自增
* 2.不使用线程守护时,循环会一直进行下去
* 3.对这个死循环线程使用守护后,当主函数结束时,死循环也结束
* @author jinlong
* */
package com.blog.part3.多线程;
import java.io.IOException;
class DaemoDemo extends Thread
{
public void run()
{
//永真循环线程
for(int i=0;;i++){
try {
//间隔1000毫秒
Thread.sleep(1000);
} catch (InterruptedException ex) { }
System.out.println(i);
}
}
public static void main(String [] args)
{
DaemoDemo test = new DaemoDemo();
test.setDaemon(true); //调试时可以设置为false,那么这个程序是个死循环,没有退出条件。设置为true,即可主线程结束,test线程也结束。
test.start();
System.out.println("isDaemon = " + test.isDaemon());
try {
System.in.read(); // 接受输入,使程序在此停顿,一旦接收到用户输入,main线程结束,守护线程自动结束
} catch (IOException ex) {}
}
}
/**
*需求:演示join的使用
*思路:创建一个线程,进行固定次数循环,主线程设计同样的固定次数循环
* 比较使用join和不使用join的变换
* */
package com.blog.part3.多线程;
public class JoinDemo extends Thread
{
public void run()
{
//for循环50次
for(int i=0;i<50;i++)
{
try
{
Thread.sleep(1000);
}
catch(InterruptedException ex){}
System.out.println("Test"+i);
}
}
public static void main(String [] args)
{
JoinDemo test=new JoinDemo();
test.start();
//加入join方法后,main线程会在test执行完之后才执行,不会抢走他的执行权
try {
test.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0;i<50;i++)
{
System.out.println("main"+i);
}
}
}
Java中,线程的优先级是介于Thread.MIN_PRIORITY到Thread.MAX_PRIORITY这两个常量之间的某个整数数值(介于1到10之间)。当利用某一线程又创建了一个新线程对象时,这个新线程将拥有与创建它的线程一样的优先级。例如,主线程的优先级默认情况下是5,那么利用主线程创建的新线程的优先级默认情况下也是5。
这里要注意一点:具有较高线程优先级的对象,只是具有较多的执行机会,而非优先执行。
MAX_PRIORITY 最高优先级10
MIN_PRIORITY 最低优先级1
NORM_PRIORITY 分配给线程的默认优先级5
Mix t1 = new Mix("Thread 1");
Mix t2 = new Mix("Thread 2");
Mix t3 = new Mix ("Thread 3");
t1.setPriority(Thread.NORM_PRIORITY-3); //设置优先级为2
t2.setPriority(Thread.NORM_PRIORITY+3); //设置优先级为8
t3.setPriority(Thread.MAX_PRIORITY);//设置优先级为10
/**
* 需求:演示yield方法的使用
* 思路:1.设计两个优先级不同的线程myThread1和myThread2
* 2.主函数设计一个循环打印输出,当变量值满足条件时,开启两个线程,同时主函数调用yield方法。
* 3.多次运行,看是否有控制作用
* @author jinlong
* */
package com.blog.part3.多线程;
public class YiledDemo
{
public static void main(String[] args)
{
//创建两个线程
Thread myThread1 = new MyThread1();
Thread myThread2 = new MyThread2();
//设置两个线程不同的优先级
myThread1.setPriority(Thread.MAX_PRIORITY);
myThread2.setPriority(Thread.MIN_PRIORITY);
//主函数线程中进行的for循环
for (int i = 0; i < 100; i++)
{
//主线程进行打印
System.out.println("main thread i = " + i);
//当i=20时,进行判断
if (i == 20)
{
//启动两个线程
myThread1.start();
myThread2.start();
//让主函数线程让步
Thread.yield();
}
}
}
}
class MyThread1 extends Thread
{
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("myThread 1 -- i = " + i);
}
}
}
class MyThread2 extends Thread
{
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("myThread 2 -- i = " + i);
}
}
}
运行结果: