线程是一种比进程更小的单位,它可以看成一个子程序,比较好理解的方式是,一个大型程序由很多进程组成,而一个进程由很多线程组成。
创建线程的两种方法
创建线程的第一种方法:创建Thread类或其子类的对象
package com.imooc.threadone;
/*
* 方式一:通过继承Thread类的方式创建线程
*/
/*
* 这里为了方便才将Thread子类和测试类写在一个java文件下
* 平时编程最好分开
*/
//Thread类是默认使用了Runnable接口的
class MyThread extends Thread
{
public MyThread()
{
}
//调用父类Thread的构造方法Thread(String name)为子类对象创建名字
public MyThread(String name)
{
super(name);
}
@Override
public void run() //重写run方法,不重写也不会报错,因为调用Runnable接口的Thread类已经重写过run方法了
{
for(int i=1;i<=10;i++)
{
//getName()方法是父类Thread中的获取线程对象名称的方法,若不手动设置则默认从Thread-0开始
System.out.println("线程"+getName()+"正在运行第"+i+"次");
}
}
}
public class ThreadTest
{
public static void main(String[] args)
{
//创建两个自定义线程MyThread类的对象,启动两个线程,多运行几次,验证线程占用CPU使用权的随机性
MyThread mt1=new MyThread("一");
MyThread mt2=new MyThread("二");
mt1.start();//启动线程,一个线程只能启动一次
mt2.start();
}
}
创建线程的第二种方法:创建Runnable接口实现类的对象
package com.imooc.runnable;
/*
* 方式二:实现Runnable接口创建线程
*/
//创建Runnable接口的实现类PrintRunnable
class PrintRunnable implements Runnable
{
public PrintRunnable()
{
}
@Override
public void run()
{
int i=1;
/*
* 由于PrintRunnable不是Thread的子类,因此无法直接调用Thread的getName()方法
* 因此直接借用Thread类下的静态方法currentThread()放回当前运行的线程对象,再调用getName()方法获取线程名
*/
while(i<=10)
{
System.out.println(Thread.currentThread().getName()+"正在运行第"+(i++)+"次");
}
}
}
public class Test
{
public static void main(String[] args)
{
//创建Runnable接口实现类PrintRunnable的对象
PrintRunnable pr=new PrintRunnable();
/*
* 利用Thread的另外一种构造方法Thread(Runnable target)创建线程对象
* target是此线程启动时调用run方法的对象,如果为null,则此类方法不执行任何操作
*/
Thread t1=new Thread(pr);
Thread t2=new Thread(pr);/*
在使用同一个Runnable接口实现类对象pr创建两个线程时,说明pr这个实例对象被两个线程共享。
因此若上面PrintRunnable类中的run()方法中的int i=1放在了方法外面,也就是成为了成员变量的话,
那么这两个线程将会共同执行一个run()方法合计十次,而不是分别执行十次.
要理解这个内容需要我们深刻理解run()方法中的代码才是线程执行的代码
*/
//启动线程
t1.start();
t2.start();
}
}
Thread类下的sleep静态方法
注意其作用是让“正在执行”的线程休眠
package com.imooc.sleep;
class MyThread implements Runnable
{
public MyThread()
{
}
@Override
public void run()
{
for(int i=1;i<=15;i++)
{
System.out.println(Thread.currentThread().getName()+"正在执行第"+i+"次");
try
{
/*
* sleep(long mills)是静态方法,因此需要通过类名的方式调用
* 并且官方API中sleep方法定义如下:
* public static void sleep(long millis) throws InterruptedException
* 因此调用的时候需要捕获并处理InterruptedException异常
*/
Thread.sleep(1000);//线程休眠1000毫秒
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
public class SleepDemo1
{
public static void main(String[] args)
{
MyThread mt=new MyThread();
Thread t1=new Thread(mt);
Thread t2=new Thread(mt);
t1.start();
t2.start();
/*
* 对于t1.start和t2.start运行的解读:
* 首先根据线程占用CPU的随机性,t1和t2两个线程都有可能首先获得CPU的占用权
* 假设t1线程首先占用CPU,那么在输出语句后,执行sleep方法进入休眠状态,此时CPU空闲,那么t2就有更大的可能去获得
* CPU的占用权(也有可能没有获取到,如果没获取到那么CPU在t1线程休眠期间就是空闲的).
* 如果在t1休眠期间有着更大几率获得CPU占用权的t2线程成功占用了CPU,那么在t2线程执行到sleep语句后进入休眠状态,
* 此时t1线程就有更大的可能获取CPU占用权,以此类推,那么程序执行的结果中t1线程和t2线程交替执行的次数应该是最多的
* 但并不全是交替执行
*/
}
}
Thread类下的join方法
1、不带参数的join方法(此方法会抛出InterruptedException类型的异常)
调用无参join方法的线程优先执行,这个线程执行完毕后其余线程才能执行
package com.imooc.join;
//继承Thread类的方法创建自定义线程类
class MyThread extends Thread
{
@Override
public void run()
{
for(int i=1;i<=10;i++)
{
System.out.println(getName()+"正在执行第"+i+"次");
}
}
}
public class Test
{
public static void main(String[] args)
{
MyThread mt=new MyThread();
mt.start();
//调用join方法同时出其抛出的异常
try
{
mt.join();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
for(int i=1;i<=20;i++)
{
System.out.println("主线程运行第"+i+"次");
}
}
}
上面程序的执行结果就是线程mt优先主线程执行,线程mt执行完毕后主线程才开始执行。
2、带参数的join方法(此方法会抛出InterruptedException类型的异常)
调用join方法的线程优先执行millis毫秒
package com.imooc.join;
class MyThread extends Thread
{
@Override
public void run()
{
for(int i=1;i<=500;i++)//由于电脑处理速度太快,因此将i设置大一点比较好观察结果
{
System.out.println(getName()+"正在执行第"+i+"次");
}
}
}
public class Test
{
public static void main(String[] args)
{
MyThread mt=new MyThread();
mt.start();
try
{
mt.join(1);//线程mt优先执行1ms,1ms后mt和主线程才开始随机分配CPU使用权
}
catch (InterruptedException e)
{
e.printStackTrace();
}
for(int i=1;i<=20;i++)
{
System.out.println("主线程运行第"+i+"次");
}
}
}
package com.imooc.priority;
class MyThread extends Thread
{
public MyThread()
{
}
public MyThread(String name)
{
super(name);
}
@Override
public void run()
{
for(int i=1;i<=10;i++)
{
System.out.println("线程"+getName()+"正在执行");
}
}
}
public class Test
{
public static void main(String[] args)
{
//利用getPriority()获取主线程的优先级(利用Thread类下的静态方法currentThread()获取当前主线程对象的引用)
int mainPriority=Thread.currentThread().getPriority();
System.out.println("主线程的优先级为:"+mainPriority);
//创建线程对象并提供线程名
MyThread mt1=new MyThread("一");
MyThread mt2=new MyThread("二");
//利用setPriority(int newPriority)方法设置线程优先级
mt1.setPriority(10);//mt1.setPriority(Thread.MAX_PRIORITY)
mt1.start();
mt2.setPriority(Thread.MIN_PRIORITY);
mt2.start();
System.out.println("线程一的优先级为:"+mt1.getPriority());
/*
* 虽然线程mt1的优先级比主线程高,但是由于CPU工作方式等原因,程序运行结果在一定程度上还是具有随机性的
* 有可能优先级低的会在优先级高的之前执行
*/
}
}
线程同步
1、银行存取款问题
存款和取款分别用两个线程实现,要执行存款方法的线程执行的时候不能被执行取款方法的线程打断,就需要使用synchronized(同步)关键字,被synchronized修饰的方法或代语句块在执行该方法的线程执行完毕前不会被其他线程打断。
线程同步学习代码
银行存取款问题代码一之Bank类
package com.imooc.bank;
public class Bank
{
private String account;//账号
private int balance;//账户余额
public Bank()
{
}
public Bank(String account,int balance)
{
this.setAccount(account);
this.setBalance(balance);
}
public void setAccount(String account)
{
this.account=account;
}
public String getAccount()
{
return this.account;
}
public void setBalance(int balance)
{
this.balance=balance;
}
public int getBalance()
{
return this.balance;
}
@Override
public String toString()
{
return "Bank [账号:=" + account + ", 余额:" + balance + "]";
}
/*
* 存款方法,固定每次存100
* 添加synchronized(同步)关键字使得存款方法内的代码在执行完之前不允许其他线程打断
* 如果没有synchronized,那么根据线程执行的不确定性,很有可能造成存款方法中的局部变量Balance实现增100后,
* 还没来得及执行setBalance(Balance)改变银行存款,便开始执行取款的线程,最终造成输出结果的错误
*/
public synchronized void saveAccount()
{
int Balance=this.getBalance();
Balance+=100;
this.setBalance(Balance);
System.out.println("存款后的余额为:"+this.getBalance());
}
//取款方法,固定每次取200
public void drawAccount()
{
synchronized(this)//利用语句块的方式实现线程同步
{
int Balance=this.getBalance();
Balance-=200;
this.setBalance(Balance);
System.out.println("取款后的余额为:"+this.getBalance());
}
}
}
线程同步学习代码
银行存取款问题代码二之存款线程类
package com.imooc.bank;
//Runnable接口的实现类SaveAccount,功能是实现存款功能的线程构造方法提供对象参数
public class SaveAccount implements Runnable
{
Bank bank;
public SaveAccount()
{
}
public SaveAccount(Bank bank)
{
this.bank=bank;
}
@Override
public void run()
{
bank.saveAccount();
}
}
线程同步学习代码
银行存取款问题代码三之取款线程类
package com.imooc.bank;
//实现取款的线程
public class DrawAccount implements Runnable
{
Bank bank;
public DrawAccount()
{
}
public DrawAccount(Bank bank)
{
this.bank=bank;
}
@Override
public void run()
{
bank.drawAccount();
}
}
线程同步学习代码
银行存取款问题代码三之测试类
package com.imooc.bank;
public class Test
{
public static void main(String[] args)
{
Bank bank=new Bank("1101",1000);
//创建Runnable接口实现类的对象
SaveAccount sa=new SaveAccount(bank);
DrawAccount da=new DrawAccount(bank);
//创建线程对象
Thread save=new Thread(sa);
Thread draw=new Thread(da);
save.start();
draw.start();
try
{
save.join();//保证优先级在主线程之前,防止提前输出bank的toString方法
draw.join();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println(bank);
}
}
输出结果为:
存款后的余额为:1100
取款后的余额为:900
Bank [账号:=1101, 余额:900]
线程通信
1、生产者消费者问题
生产者线程负责生产商品Queue,消费者线程负责消费商品Queue,要求生产多少就得消费多少,即生产消费必须成对出现,在没有商品的时候不得消费,在有商品的时候不得生产。
常用方法(Object类下的方法)
线程通信
学习代码一之商品Queue类
package com.imooc.queue;
//相当于商品类
public class Queue
{
private int n;//相当于商品数量
/*
* flag为false相当于商品消费完毕,不允许执行消费线程
* flag为true相当于商品生产完毕,不允许再执行生产线程
*/
boolean flag=false;
public Queue()
{
}
//消费商品的方法
public synchronized int get()
{
if(!flag)
{
try
{
wait(); //如果商品消费完了,则等待唤醒
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
System.out.println("消费:"+n);
flag=false;//消费完毕
/*
* notifyAll()唤醒所有处于wait状态的线程,单独一个notify()只能随机唤醒一个线程,并不能指定唤醒线程
* 如果没有唤醒语句则有可能出现死锁的情况,即线程都处于wait状态中
* 死锁出现的原因:
* 刚开始flag是false,如果先执行get,那么执行get方法的线程会处于wait状态,此后执行set方法的线程,
* 完毕后flag置为true,但此时执行get方法的线程依然处于wait状态,而由于flag为true,
* 执行set方法的线程占用CPU后也会执行wait语句进入等待状态
* 因此两个线程均进入wait状态,即发生死锁
*/
notifyAll();
return n;
}
//生产商品的方法
public synchronized void set(int N)
{
if(flag)
{
try
{
wait();//如果有商品,则不再生产,等待唤醒,唤醒后沿着wait语句往后执行
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
System.out.println("生产:"+N);
flag=true;//生产完毕
/*
* notifyAll()唤醒所有处于wait状态的线程,单独一个notify()只能随机唤醒一个线程,并不能指定唤醒线程
* 如果没有唤醒语句则有可能出现死锁的情况,即线程都处于wait状态中
*/
this.n = N;
notifyAll();
}
}
线程通信
学习代码二之生产者线程类
package com.imooc.queue;
//生产者线程类(只是通俗的叫法,实际上并不是Thread的子类),主要实现的功能是增加商品
public class Producer implements Runnable
{
Queue queue;
public Producer()
{
}
public Producer(Queue queue)
{
this.queue=queue;
}
@Override
public void run()
{
int i=0;
while(true)
{
queue.set(i++);//增加商品
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
线程通信
学习代码三之消费者线程类
package com.imooc.queue;
//消费者线程类,主要的功能是减少商品
public class Consumer implements Runnable
{
Queue queue;
public Consumer()
{
}
public Consumer(Queue queue)
{
this.queue=queue;
}
@Override
public void run()
{
while(true)
{
queue.get();
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
线程通信
学习代码四之测试类
package com.imooc.queue;
public class Test
{
public static void main(String[] args)
{
Queue queue=new Queue();
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
}
}