多线程的生成方法
Java多线程有多种构建方法:
- 一个类继承Thread类,重写run方法,调用这个类的对象的start方法,就可以将这个线程启动
- 一个类实现Runnable接口,重写run方法,使用Thread类来代理这个类的对象,再调用start方法
- 类实现Callable接口
构建代码:
public class makethread {
public static void main(String[] args) {
th1 t1=new th1();
t1.start();
th2 t2=new th2();
new Thread(t2).start();
//使用lambda表达式
new Thread(()->{
for(int i=0;i<10;i++) {
System.out.println(i);
}
}).start();
}
}
//方法1
class th1 extends Thread {
public void run() {
System.out.println("this is thread 1");
}
}
//创建线程的第二种方法(推荐),因为有单继承的限制,所以尽量使用接口
class th2 implements Runnable {
public void run() {
System.out.println("this is thread 2");
}
}
线程的生命周期
关于生命周期的一些函数的使用:
- start:调用start方法后,线程进入就绪状态,等待cpu的调度
- sleep:sleep方法需要传入一个参数,表示线程休眠的时间,单位是毫秒。sleep方法执行后在休眠时间内线程会处于阻塞状态,sleep函数造成的阻塞状态不会放弃cpu的资源,可以理解为“抱着资源休眠一段时间”
- wait:wait函数也是使线程进入阻塞状态,但是与sleep不同的是,wait不会自己醒来,必须要别的线程来唤醒才可以从阻塞状态进入就绪状态,继续等待cpu的调用。而且wait方法阻塞后会释放资源
- notify/notifyAll:唤醒其他wait线程
- join:如在B线程中调用A.join(),则表示B需要等待A线程执行完再继续向下执行
- yield:让出资源,将调用它的线程直接转为就绪状态
- setDemon:将指定的线程转换为守护线程
- stop:停止线程,线程进入死亡状态,但是不推荐使用,推荐使用标志位的方式来停止一个线程
线程同步
线程资源共享
线程间的资源共享有多种方式,每种方式都有不同的作用:
1、多个线程共享一个对象的私有变量:使用创建一个实现了Runable
接口的对象,用多个线程代理此对象,但是这样每个线程只能实现同样的操作,一下是模拟的售票系统
public class Tsharedtest {
public static void main(String[] args) {
share1();
}
//使用runnable接口共享私有成员变量
public static void share1() {
taketic t1=new taketic();
//此处应该用同一个对象创建线程
new Thread(t1,"thread1").start();;
new Thread(t1,"thread2").start();;
new Thread(t1,"thread3").start();;
}
}
class taketic implements Runnable{
//资源
private int tic=10;
@Override
public void run() {
while(true) {
if(tic<0)
break;
System.out.println(Thread.currentThread().getName()+"-----"+tic--);
}
}
}
2、共享某个类的静态变量
3、共享某个对象:使用一个普通的类创建一个对象,将这个对象传入多个线程,就可以实现多个线程共享一个对象了,且每个线程可以实现不同的操作。以下代码是模拟的经典的生产者/消费者模型
public class CoTest01 {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
//生产者
class Productor extends Thread{
SynContainer container ;
public Productor(SynContainer container) {
this.container = container;
}
public void run() {
//生产
for(int i=0;i<100;i++) {
System.out.println("生产-->"+i+"个馒头");
container.push(new Steamedbun(i) );
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container ;
public Consumer(SynContainer container) {
this.container = container;
}
public void run() {
//消费
for(int i=0;i<100;i++) {
System.out.println("消费-->"+container.pop().id+"个馒头");
}
}
}
//缓冲区
class SynContainer{
Steamedbun[] buns = new Steamedbun[10]; //存储容器
int count = 0; //计数器
//存储 生产
public synchronized void push(Steamedbun bun) {
//何时能生产 容器存在空间
//不能生产 只有等待
if(count == buns.length) {
try {
this.wait(); //线程阻塞 消费者通知生产解除
} catch (InterruptedException e) {
}
}
//存在空间 可以生产
buns[count] = bun;
count++;
//存在数据了,可以通知消费了
this.notifyAll();
}
//获取 消费
public synchronized Steamedbun pop() {
//何时消费 容器中是否存在数据
//没有数据 只有等待
if(count == 0) {
try {
this.wait(); //线程阻塞 生产者通知消费解除
} catch (InterruptedException e) {
}
}
//存在数据可以消费
count --;
Steamedbun bun = buns[count] ;
this.notifyAll(); //存在空间了,可以唤醒对方生产了
return bun;
}
}
//馒头
class Steamedbun{
int id;
public Steamedbun(int id) {
this.id = id;
}
}
也可以使用一个标志位来判断线程是否需要等待,这样可应用于资源数只有一个的时候。
线程的锁机制
为了防止多个线程操作一个数据时发生同时修改导致数据错误的情况,使用锁机制可以规定某一个资源在同一时间只能有一个线程在访问。
锁的关键字为synchronized
锁的机制分两种:
1、对象锁:用法是synchronized(object e){},这种方法锁住的是对象,如果修改的是本对象内的另一个对象,而使用的是synchronized(this){},就达不到加锁的目的了。在方法前加上synchronized实际上也是对象锁
2、类锁:用法是synchronized(object.class){},或者直接用synchronized修饰静态方法,类锁的实际意思就是锁住了类的class对象,所以在创建新对象、使用静态方法、静态变量时可以使用这样的锁。
一般的只要有多个线程修改一个对象,或者想控制某一个对象只能同时被一个线程使用的情况,都要用到锁机制