1. 常用方法
Start(): 启动线程的唯一方式。
setName(): 设置线程的名字
getName(): 获取线程的名字
setPriority(): 设置线程优先级
getPriority(): 获取线程优先级
static currentThread(): 获取当前线程的内存地址
static sleep(): 睡眠当前线程,参数是睡眠的毫秒数。
public class Test{
public static void main(String[]args){
Thread t1 = new Processor();
Thread t2 = new Processor();
Thread t3= new Processor();
t1.start();
t2.start();
t3.start();
}
}
class Processor extends Thread{
@Override
public void run(){
System.out.println(Thread.currentThread().getName()+"开始了");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"结束了");
}
}
2. 生命周期
JDK中用Thread.State类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类 及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五 种状态:
新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建 状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线 程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
3. 终止线程
stop 已经过时,不推荐使用,因为可能会导致死锁。
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test05{
public static void main(String[]args){
Thread t1 = new Thread(new A());
t1.setName("t1");
t1.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
new A().flag = false;
}
}
class A implements Runnable{
boolean flag = true;
public void run(){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
while(flag){
System.out.println(Thread.currentThread().getName() + " : "
+ sdf.format(new Date()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
4.线程控制
setPriority(): 设置线程优先级
getPriority(): 获取线程优先级
Thread.sleep(): 将当前线程睡眠
join(): 调用某线程的该方法,将当前线程和该线程合并,即等待该线程结束,再恢复当前线程的运行。
yield(): 让出CPU,当前线程进入就绪队列等待调度
wait(): 当前线程挂起。
notify(): 唤醒挂起的线程。
5. 线程同步
5.1 概述
1. 多线程出现了安全问题
2.问题的原因
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还有执行完,另一个线程参与进来,导致共享数据的错误。
3. 解决办法
对多态操作共享数据的语句,只让一个线程都执行完,执行过程中q,其他线程不可以参与执行。
6. Lock
6.1 概述
1. Lock 是显式锁需要手动开启和手动关闭,而synchronized是隐式锁,出了作用域,自动解锁。
2. Lock 只有代码块锁,而synchronized可以代码块锁,可以方法锁
3. 使用Lock还有更多功能扩展。
6.2 使用
7. 死锁
7.1 概述
死锁:不同的线程分别占用了对方需要的同步资源不放弃,都等待对方放弃自己需要的同步资源。
出现死锁后,不会出现异常,不会出现提示,只是所以的线程都处于阻塞状态。
解决方法:专门的算法、尽量减少同步资源的定义、尽量避免嵌套同步。
7.2 原理
死锁:就是大家在执行过程中,都遇到了对方进入加锁的方法,从而导致都访问不乱哦的状态。
1.某个线程执行完成,需要先后嵌套锁定两个对象,在这个过程中,该线程先锁定了第一个对象。
2.另一个线程执行完成,需要先后嵌套锁定两个对象,在这个过程中,该线程先锁定了第二个对象。
3.第一个线程执行中,执行到锁第二个对象的时候,发现第二个对象,被第二个线程锁住了,等待。
3.第二个线程执行中,执行到锁第一个对象的时候,发现第一个对象,被第一个线程锁住了,等待。
7.3 编码
public class Test10{
public static void main(String[]args){
for(int i= 0; i<100;i++){
Object o1 = new Object();
Object o2 = new Object();
Thread t1 = new A(o1,o2);
Thread t2 = new Thread(new B(o1,o2));
t1.start();
t2.start();
}
}
}
class A extends Thread{
Object o1;
Object o2;
public A(Object o1,Object o2){
this.o1 =o1;
this.o2 =o2;
}
public void run(){
synchronized(o1){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(o2){
System.out.println("A已经对o2加锁了");
}
}
}
}
class B implements Runnable{
Object o1;
Object o2;
public B(Object o1,Object o2){
this.o1 =o1;
this.o2 =o2;
}
public void run(){
synchronized(o2){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(o1){
System.out.println("B已经对o1加锁了");
}
}
}
}
8. 线程通信
8.1 概述
a. wait(): 令当前线程挂起并放弃CPU、同步资源并等待,识别的线程可以访问并修改共享资源。
b.notify(): 唤醒正在排队等待同步资源的线程中的任意一个。
c.notifyAll(): 唤醒正在排队登台资源的所有线程结束等待。
这三个方法只有在 synchronized 方法或 synchronized 代码块中才能使用。
9. 生产者和消费者
生产者消费者模式并不是GOF提出的23种设计模式之一,23种设计模式都是建立在面向对象的基础之上的,但其实面向过程的编程中也有很多高效的编程模式,生产者消费者模式便是其中之一,它是我们编程过程中最常用的一种设计模式。
在实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。
单单抽象出生产者和消费者,还够不上是生产者/消费者模式。该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。大概的结构如下图。
优点:比如我们现在取快递或者取外面,一般我们上课或者上班 ,不方便拿快递,所以 只需要把快递放到相应的投放点即可,比如菜鸟驿站,此时 菜鸟驿站就是这个缓冲区,而我们和快递员之间的耦合度就降低了,相互之间也不用认识
public class Test11{
public static void main(String[] args) {
SynStack ss = new SynStack();
Thread t1 = new Thread(new Producer(ss));
Thread t2 = new Thread(new Consumer(ss));
t1.start();
t2.start();
}
}
// 生产者线程
class Producer implements Runnable{
SynStack ss = null;
public Producer(SynStack ss) {
this.ss = ss;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
char ch = (char) (i+97);
ss.push(ch);
}
}
}
// 消费者线程
class Consumer implements Runnable{
SynStack ss = null;
public Consumer(SynStack ss) {
this.ss = ss;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
ss.pop();
}
}
}
// 业务类
class SynStack {
// 缓冲区
char[] data = new char[6];
// 已生产的个数
int cnt = 0;
// 生产方法
public synchronized void push(char c){
// 缓冲区生产满之后,就不再生产
if (cnt == data.length) {
// 进入挂起状态,并交出锁
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 到这里说明没满,应该生产产品,然后唤醒消费者开始消费
data[cnt] = c;
cnt++;
System.out.println("生产了 : "+c+" , 目前剩余 : "+cnt+" 个字符");
this.notifyAll();
}
// 消费方法
public synchronized char pop(){
char ch = ' ';
// 判断个数是否是0, 是0说明消费完了,就进入挂起
if (cnt == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
cnt--;
ch = data[cnt];
System.out.println("消费了 : "+ch+" , 目前剩余 : "+cnt+" 个字符");
this.notifyAll();
return ch;
}
}
10. 单例模式
让某个类只实例化一次对象
1 构造方法私有化
2 静态变量保存创建的对象
3 公共的静态方法,用于获取当前类对象
public class Test12 {
public static void main(String[] args) {
Thread t1 = new A();
Thread t2 = new A();
Thread t3 = new A();
t1.start();
t2.start();
t3.start();
}
}
class A extends Thread{
@Override
public void run() {
System.out.println(Test.getInstance());
}
}
public class Test {
private Test() {
}
// 防止指令重排
private volatile static Test s = null;
public static Test getInstance() {
if (s == null) {
// 类锁,类中所有加锁的静态方法和语句块锁 全部锁定
synchronized(Test.class){
// 双重校验
if ( s == null) {
s = new Test();
}
}
}
return s;
}
}