一、Java线程
1、Java线程实现方式:Java中实现多线程主要有两种方式,通过extends Thread类的方式来实现;另一种通过implements Runnable接口来实现。
2、线程的生命周期:Java中,一个线程的生命周期有4种状态,初始化、可执行、阻塞、死亡。
- 初始化状态:通过new语句创建一个线程对象。
- 可执行状态:调用start()方法,线程分配到了CPU时间,或者等待分配CPU时间。
- 阻塞状态:通过调用sleep()方法或wait()方法,线程进入挂起状态,线程不会分配到CPU时间。
- 死亡状态:run()方法中的逻辑正常运行结束进入死亡状态;调用stop()方法或destroy()方法时也会非正常地终止当前线程。
二、创建Java线程
Java中创建一个子线程用到Thread类和Runnabe接口。Thread是线程类,创建一个Thread对象就是创建一个新线程,线程执行的代码程序是在实现Runnable接口对象的run()方法中编写的,即线程执行对象。
1、继承Thread线程类
Thread类也实现了Runnable接口,所以Thread类也可以作为线程执行对象,需要继承Thread类,覆盖run()方法。通过start()方法来启动该线程,它会触发run()方法。
创建继承thread类
public class SimpleThread extends Thread{
int index;//线程编号
//通过构造函数指定该线程编号
public SimpleThread(int index) {
this.index=index;
System.out.println("创建线程"+index);
}
//定义线程的运行代码
public void run()
{
for(int i=0;i<=3;i++)
{
System.out.println("线程"+index+" : "+i);
}
System.out.println("线程"+index+" : "+index);
}
}
测试该线程类
public static void main(String[] args) {
for(int j=0;j<3;j++)
{
Thread t=new SimpleThread(j+1);
t.start();
}
}
2、实现Runnable接口
Java语言中一个类不能继承多个类,如果一个类已经继承另一个类,就无法再继承thread类,这时可以实现Runnable的方式实现多线程。
public class ThreadPriority implements Runnable {
int numble;//线程编号
public ThreadPriority(int numble) {
this.numble=numble;
System.out.println("创建线程"+numble);
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<=3;i++)
{
System.out.println("线程"+numble+" : "+i);
}
System.out.println("线程"+numble+" : "+numble);
}
}
调用线程
public static void main(String[] args) {
for(int j=0;j<3;j++)
{
Thread t=new Thread(new ThreadPriority(j+1));
t.start();
}
}
如果线程体使用的地方不多,可以不用单独定义一个类,使用匿名内部类或Lambda表达式直接实现Runnable接口,不需要定义一个线程类文件,使得代码变得简洁。
(1)使用匿名内部类
public static void main(String[] args) {
Thread t1=new Thread(new Runnable() {//此处使用Thread(Runnable target)构造方法
//编写执行线程代码
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<=3;i++)
{
System.out.println(i);
}
System.out.println("执行完成"+Thread.currentThread().getName());
}
});
t1.start();
}
(2)使用Lambda表达式
public static void main(String[] args) {
Thread t1=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
//编写执行线程代码
// TODO Auto-generated method stub
for(int i=0;i<=3;i++)
{
System.out.println(i);
}
System.out.println("执行完成"+Thread.currentThread().getName());
});
t1.start();
}
3、设置线程优先级
Java语言把线程分成了10个不同的优先级别,用1-10表示,数字越小级别越高。Thread类的setPriority(int newPriority)方法可以设置线程优先级,通过getPriority()方法获得线程优先级。
public static void main(String[] args) {
Thread t1=new Thread(new ThreadPriority(1));//线程1
t1.setPriority(3);//设置线程优先级别为3
t1.start();
Thread t2=new Thread(new ThreadPriority(2));//线程2
t2.setPriority(2);//设置线程优先级别为2
t2.start();
}
4、线程让步
线程类Thread中的静态方法yield(),可以使当前线程给其他线程让步。
public static void main(String[] args) {
Thread t1=new Thread(new ThreadPriority(1));//线程1
t1.setPriority(3);//设置线程优先级别为3
t1.start();
Thread t2=new Thread(new ThreadPriority(2));//线程2
t2.setPriority(2);//设置线程优先级别为2
t2.start();
Thread.yield();//当前线程让步
}
5、线程休眠
Thread类的sleep()静态方法,可以让当前线程阻塞指定时间。
注意:调用sleep()方法时,必须要包含在try...catch代码中,否则会有语法错误。
try {
Thread.sleep(1000);
}
catch(InterruptedException e)
{
}
三、多线程同步
多线程对临界资源的访问有时会导致数据的不一致性。即启动多个线程时,它们可能并发执行某个方法或某块代码,从而可能会发生不同线程同时修改同块存储空间内容的情况。
Java提供了互斥机制,在任意时刻只能由一个线程访问,即使该线程出现阻塞,该对象的被锁定状态也不会被解除,其他线程仍不能访问该对象。可以通过synchronized关键字实现同步,一种是使用synchronized关键字修饰方法,对方法进行同步;
以下两种写法一致:
另一种是使用synchronized关键字放在对象前面限制一段代码的执行。
- 对于实例方法,要给调用该方法的对象加锁。
- 对于静态方法,要给这个类加锁。
应用举例:
编写售票系统,ticketCount为当前票数,getTicketCount()方法获得当前票数,sellTicket()方法销票。
不加同步机制的运行结果如下所示:
1、使用synchronized修饰方法实现线程同步
对getTicketCount()方法和sellTicket()方法使用synchronized修饰
public class TicketDB {
private int ticketCount=5;//机票数量
//获得当前机票数量
public synchronized int getTicketCount() {
return ticketCount;
}
//销售机票
public synchronized void sellTicket() {
try {
Thread.sleep(1000);//阻塞当前线程,模拟用户付款
}
catch(InterruptedException e) {
}
System.out.printf("第%d号票,已售出\n",ticketCount);
ticketCount--;
}
}
测试:
public static void main(String[] args) {
TicketDB db=new TicketDB();
//模拟1号售票网点
Thread t1=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
//编写执行线程代码
while(true) {
int currTicketCount=db.getTicketCount();//获取当前票数
//查询是否有票
if(currTicketCount>0) {
db.sellTicket();
}else {
break;
}
}
});
t1.start();
//模拟2号售票网点
Thread t2=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
//编写执行线程代码
while(true) {
int currTicketCount=db.getTicketCount();//获取当前票数
//查询是否有票
if(currTicketCount>0) {
db.sellTicket();
}else {
break;
}
}
});
t2.start();
}
运行结果:
2、使用synchronized语句
getTicketCount()方法和sellTicket()方法为普通方法,不用synchronized修饰
public static void main(String[] args) {
TicketDB db=new TicketDB();
//模拟1号售票网点
Thread t1=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
//编写执行线程代码
while(true) {
synchronized (db) {
int currTicketCount=db.getTicketCount();//获取当前票数
//查询是否有票
if(currTicketCount>0) {
db.sellTicket();
}else {
break;
}
}
}
});
t1.start();
//模拟2号售票网点
Thread t2=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
//编写执行线程代码
while(true) {
synchronized (db) {
int currTicketCount=db.getTicketCount();//获取当前票数
//查询是否有票
if(currTicketCount>0) {
db.sellTicket();
}else {
break;
}
}
}
});
t2.start();
}
四、线程通信
1、wait和notify方法
(1)wait和notify需要放置在synchronized的作用域中。wait属于object类,一旦一个线程执行wait方法后,该线程就会释放synchronized所关联的锁(对象锁),进入阻塞状态,所以该线程一般无法再次主动回到可执行状态,一定要通过其他线程的notify方法去唤醒它。
notifyAll会让所有因wait方法进入阻塞状态的线程退出阻塞状态,这些线程会竞争对象锁,如果其中一个线程获得了对象锁,则会继续执行。在它释放锁后,其他已被唤醒的线程继续竞争。
(2)一旦一个线程执行了notify方法,则会通知那些可能因调用wait方法而等待对象锁的其他线程。如果有多个线程等待,则任意挑选一个线程,通知该线程得到对象锁从而继续执行下去。
应用举例:使用wait和notify方法实现生产者和消费者模型
public class Stack {
//堆栈指针初始值0
private int pointer=0;
//定义堆栈字符空间
private char [] data=new char[5];
//压栈
public synchronized void push(char c) {
//堆栈已满,不能压栈
while(pointer==data.length) {
try {
this.wait();//等待,直到有数据出栈
}catch(InterruptedException e) {
}
}
//通知其他线程把数据出栈
this.notify();
//数据压栈
data[pointer]=c;
pointer++;//指针向上移动
}
//出栈
public synchronized char pop() {
//堆栈无数据,不能出栈
while(pointer==0) {
try {
this.wait();//等待其他线程把数据压栈
}catch(InterruptedException e) {
}
}
//通知其他线程压栈
this.notify();
pointer--;//指针向下移动
return data[pointer];
}
}
测试:
public static void main(String[] args) {
Stack stack =new Stack();
//生成者线程
Thread t1=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
//编写执行线程代码
char c;
for(int i=0;i<10;i++) {
c=(char)(Math.random()*26+'A');//随机产生10个字符
stack.push(c);//字符压栈
System.out.println("生成: "+c);
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
}
}
});
//消费者线程
Thread t2=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
//编写执行线程代码
char c;
for(int i=0;i<10;i++) {
c=stack.pop();//从堆栈取字符
System.out.println("消费: "+c);
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
}
}
});
t1.start();
t2.start();
}