多线程
创建线程
创建线程三种方式
- 继承Thread类,重写run方法
- 实现Runnable接口
- 实现Callable接口
继承Thread类
继承Thread类,重写run方法
package Thread;
public class MyThread extends Thread{
@Override
public void run() {
for(int i = 0;i<100;i++){
System.out.println("子线程..........."+i);
}
}
}
package Thread;
public class TestThread {
public static void main(String[] args) {
//创建线程对象
MyThread myThread = new MyThread();
//2启动线程,使用run方法相当于对象调用run方法,单线程
myThread.start();
//主线程执行
for (int i = 0;i < 50;i++){
System.out.println("主线程============"+i);
}
}
}
获取线程名称
获取线程ID和线程名称
1.在Thread的子类中调用this.getId()或this.getName()
2.使用Thread.currentThread().getId()和Thread.currentThread ().getName ()
3.修改线程名称
调用线程对象的setName()方法。
使用线程子类的构造方法赋值。
public void run() {
for(int i = 0;i<100;i++){
//this.getId获取线程Id
//this.getName获取线程名称
//第一种方法,需要继承Thread类
System.out.println("线程id:"+this.getId()+"线程名称"+getName()+"子线程..........."+i);
}
//第二种方式Thread.currentThread() 获取当前线程
System.out.println("线程id:"+Thread.currentThread()+"线程名称:"+Thread.currentThread().getName());
获取和修改线程名称
修改线程名称
调用线程对象的setName()方法。
使用线程子类的构造方法赋值。
//修改线程名称
MyThread myThread = new MyThread();
myThread.setName("我的子线程1");
myThread.start();
MyThread myThread2 = new MyThread();
myThread2.setName("我的子线程2");
myThread2.start();
第二种方法:用super把name传给Thread类
//添加构造方法
public MyThread(){
}
public MyThread(String name){
super(name);//用super把name传给Thread类
}
MyThread myThread = new MyThread("我的子线程1");
MyThread myThread2 = new MyThread("我的子线程2");
示例
使用继承Thread类实现4个窗口各卖100张票
package Thread;
public class TicketWin extends Thread{
//添加构造方法,添加后可以简去getName
public TicketWin(){}
public TicketWin(String name){
super(name);
}
private int ticket = 100;//票数
@Override
public void run() {
//卖票功能
while(true){
if(ticket<=0){
break;
}
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
}
package Thread;
public class TestWin {
public static void main(String[] args) {
//创建四个窗口
TicketWin w1 = new TicketWin("窗口1");
TicketWin w2 = new TicketWin("窗口2");
TicketWin w3 = new TicketWin("窗口3");
TicketWin w4 = new TicketWin("窗口4");
//启动线程
w1.start();
w2.start();
w3.start();
w4.start();
}
}
创建线程(2)
第二种方式:实现Runnable接口
package Thread;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0;i < 100;i++){
System.out.println(Thread.currentThread().getName()+"...."+i);
}
}
}
package Thread;
public class TestRunnable {
public static void main(String[] args) {
//1创建MyRunnable对象,表示要执行的功能
MyRunnable runnable = new MyRunnable();
//2创建线程对象
Thread thread = new Thread(runnable,"我的线程1");
//3启动
thread.start();
for (int i = 0;i<50;i++){
System.out.println("main....."+i);
}
}
}
匿名内部类
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0;i<50;i++){
System.out.println("main....."+i);
}
}
};
//创建线程对象
Thread thread = new Thread(runnable,"我的线程1");
thread.start();
}
}
Runnable案例1
1.实现4个窗口共卖100张票
package Thread2;
public class Ticket implements Runnable{
private int ticket = 100;//100张票
@Override
public void run() {
while(true){
if (ticket<=0){
break;
}
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
}
package Thread2;
public class TestTicket {
public static void main(String[] args) {
//1创建票对象
Ticket ticket = new Ticket();
//2创建线程对象
Thread w1 = new Thread(ticket,"窗口1");
Thread w2 = new Thread(ticket,"窗口2");
Thread w3 = new Thread(ticket,"窗口3");
Thread w4 = new Thread(ticket,"窗口4");
//3启动线程
w1.start();
w2.start();
w3.start();
w4.start();
}
}
问题:四个窗口会抢夺100张票
Runnable案例2
2.你和你女朋友共用一张银行卡,你向卡中存钱,你女朋友从卡中取钱,使用程序
模拟过程?
创建两个类,分别为存钱和取钱
package Thread2;
public class AddMoney implements Runnable{
private BankCard card;//BankCard类的card
public AddMoney(BankCard card){
this.card = card;
}
@Override
public void run() {
for (int i = 0 ; i<10;i++){
card.setMoney(card.getMoney()+1000);
System.out.println(Thread.currentThread().getName()+"存了1000,余额是:"+card.getMoney());
}
}
}
package Thread2;
public class SubMoney implements Runnable{
private BankCard card;
public SubMoney(BankCard card){
this.card = card;
}
@Override
public void run() {
for (int i = 0;i<10;i++){
if(card.getMoney()>=1000){
card.setMoney(card.getMoney()-1000);
System.out.println(Thread.currentThread().getName()+"取了1000,余额是:"+card.getMoney());
}else{
System.out.println("余额不足,请赶快存钱");
i++;
}
}
}
}
启动
package Thread2;
public class TestBankCard {
public static void main(String[] args) {
//1创建一张银行卡
BankCard card = new BankCard();
//2创建存钱,取钱
AddMoney add = new AddMoney(card);
SubMoney sub = new SubMoney(card);
//3创建两个线程
Thread zhangge = new Thread(add,"张哥");
Thread jingjing = new Thread(sub,"静静");
//4启动线程
zhangge.start();
jingjing.start();
//有线程安全问题
}
}
简介写反,用匿名内部类
package Thread2;
public class TestBankCard2 {
public static void main(String[] args) {
BankCard card = new BankCard();
//存钱
Runnable add = new Runnable() {
@Override
public void run() {
for (int i = 0 ; i<10;i++){
card.setMoney(card.getMoney()+1000);
System.out.println(Thread.currentThread().getName()+"存了1000,余额是:"+card.getMoney());
}
}
};
//取钱
Runnable sub = new Runnable() {
@Override
public void run() {
for (int i = 0;i<10;i++){
if(card.getMoney()>=1000){
card.setMoney(card.getMoney()-1000);
System.out.println(Thread.currentThread().getName()+"取了1000,余额是:"+card.getMoney());
}else{
System.out.println("余额不足,请赶快存钱");
i++;
}
}
}
};
//创建线程对象,并启动
new Thread(add,"明明").start();
new Thread(sub,"小红").start();
}
}
线程状态(基本)
线程休眠
休眠:
public static void sleep(long millis)
当前线程主动休眠 millis 毫秒
package Thread2;
public class TestSleep extends Thread{
public static void main(String[] args) {
SleepThread s1 = new SleepThread();
s1.start();
SleepThread s2 = new SleepThread();
s2.start();
}
@Override
public void run() {
for (int i = 0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"..."+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}//子类不能抛出比父类更广的异常,要用try/catch
}
}
}
放弃:
public static void yield()
当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。
package Thread2;
public class YieldThread extends Thread{
@Override
public void run() {
for (int i = 0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"..."+i);
//主动放弃cpu
Thread.yield();
}
}
}
线程加入:
public final void join()
允许其他线程加入到当前线程中
j1.join();//加入当线程(main),并阻塞当前线程,直到加入线程执行完毕
package Thread2;
public class TestJoin extends Thread{
public static void main(String[] args) {
JoinThread j1 = new JoinThread();
j1.start();
try {
j1.join();//加入当线程(main),并阻塞当前线程,直到加入线程执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
//for 主线程
for(int i = 0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"======"+i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
for(int i = 0;i<30;i++){
System.out.println(Thread.currentThread().getName()+"..."+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
设置线程优先级,守护线程
优先级:
线程对象.setPriority(),
线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多。
守护线程:
线程对象.setDaemon(true);设置为守护线程
线程有两类:用户线程(前台线程)、守护线程(后台线程)。
如果程序中所有前台线程都执行完毕了,后台线程会自动结束。
垃圾回收器线程属于守护线程。
package Thread2;
public class PriorityThread extends Thread{
@Override
public void run() {
for (int i = 0;i<50;i++){
System.out.println(Thread.currentThread().getName()+"===="+i);
}
}
public static void main(String[] args) {
PriorityThread p1 = new PriorityThread();
p1.setName("p1");
PriorityThread p2 = new PriorityThread();
p2.setName("p2");
PriorityThread p3 = new PriorityThread();
p3.setName("p3");
p1.setPriority(1);
p3.setPriority(10);
//启动
p1.start();
p2.start();
p3.start();
}
}
守护线程
package Thread2;
public class TestDeamon {
public static void main(String[] args) {
//创建线程(默认前台线程)
DeamonThread d1 = new DeamonThread();
//设置为守护线程前台线程(d1)执行完之后,后台线程(main)会结束
d1.setDaemon(true);
d1.start();
for (int i = 0;i<10;i++){
System.out.println("主线程-------------");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程的状态(等待)
线程安全问题
package Thread2;
import java.util.Arrays;
public class ThreadSafe {
private static int index = 0;
public static void main(String[] args) throws Exception {
//创建数组
String[] s = new String[5];
//创建两个操作
Runnable runnableA = new Runnable() {
@Override
public void run() {
s[index] = "hello";
index++;
}
};
Runnable runnableB = new Runnable() {
@Override
public void run() {
s[index] = "world";
index++;
}
};
//创建两个线程对象
Thread a = new Thread(runnableA,"A");
Thread b = new Thread(runnableB,"B");
a.start();
b.start();
a.join();
b.join();
System.out.println(Arrays.toString(s));
}
}
有可能一条线程并没有执行完毕,另一条线程就执行覆盖了上一条线程的结果
线程安全
同步方式
synchronized(临界资源对象){//对临界资源对象枷锁
//代码(原子操作),先运行
}
每个对象都有一个互斥锁标记,用来分配给线程的。
只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。线程退出同步代码块时,会释放相应的互斥锁标记。
我的理解是,加锁之后要等代码(原子操作)执行完成后再有下一步操作
加锁之后别的线程只能等正在运行的代码完成后才能运行
Runnable runnableA = new Runnable() {
@Override
public void run() {
//同步代码块
synchronized (s){
s[index] = "hello";
index++;
}
}
};
Runnable runnableB = new Runnable() {
@Override
public void run() {
synchronized (s){
s[index] = "world";
index++;
}
}
};
同步代码块使用(1)
@Override
public void run() {
while(true){
if (ticket<=0){
break;
}
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
分析:有4个窗口,100张票。假如当第1个窗口拿到CPU后会执行,卖出第100张票,但可能还没ticket - -,时间片段就到了,这时候ticket还是100张,这时候第2个窗口就拿到了CPU,卖出的也是第100张票 ,以此类推,会出现重复。
public class Ticket implements Runnable{
private int ticket = 100;//100张票
//创建锁
private Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj){
if (ticket<=0){
break;
}
}
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
}
不能在锁里的临界资源对象处创建对象,synchronized(new Object()),这样等于每次每个线程进来都创建一个锁。相当于一个房子,一个人(线程)只能有一个锁,用完出去之后才能换另外一个人(线程),如果都创建一个锁,别的人(线程)还是会进入。
可以
private int obj = new Object();
synchronized(new Object())
也可以
synchronized(this);
这个this就是ticket,也是唯一的
同步代码块使用(2)
package Thread2;
public class TestBankCard {
public static void main(String[] args) {
BankCard card = new BankCard();
Object obj =new Object();
//存钱
Runnable add = new Runnable() {
@Override
public void run() {
for (int i = 0 ; i<10;i++){
synchronized (obj){
card.setMoney(card.getMoney()+1000);
System.out.println(Thread.currentThread().getName()+"存了1000,余额是:"+card.getMoney());
}
}
}
};
//取钱
Runnable sub = new Runnable() {
@Override
public void run() {
for (int i = 0;i<10;i++){
synchronized (obj){
if(card.getMoney()>=1000){
card.setMoney(card.getMoney()-1000);
System.out.println(Thread.currentThread().getName()+"取了1000,余额是:"+card.getMoney());
}else{
System.out.println("余额不足,请赶快存钱");
i--;
}
}
}
}
};
new Thread(add,"明明").start();
new Thread(sub,"小红").start();
}
}
同步方法
同步方法:
synchronized 返回值类型方法名称(形参列表0){ //对当前对象(this)加锁
//代码(原子操作)
}
只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。
线程退出同步方法时,会释放相应的互斥锁标记。
public class Ticket implements Runnable {
private int ticket = 100;//100张票
//创建锁
//private Object obj = new Object();
@Override
public void run() {
while (true) {
if(!sale()){
break;
}
}
}
//卖票
public synchronized boolean sale() {//锁 this 静态方法 锁 Ticket.class
if (ticket <= 0) {
return false;
}
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
ticket--;
return true;
}
}
同步规则
注意:
只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。
如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。
已知JDK中线程安全的类:
StringBuffer
Vector
Hashtable
以上类中的公开方法,均为synchonized修饰的同步方法。
死锁
死锁:
当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。
public class Boy extends Thread{
@Override
public void run() {
synchronized (MyLock.a) {
System.out.println("男孩拿到了a");
synchronized (MyLock.b) {
System.out.println("男孩拿到了b");
System.out.println("男孩可以吃东西了");
}
}
}
}
public class Girl extends Thread{
@Override
public void run() {
synchronized (MyLock.b) {
System.out.println("女孩拿到了b");
synchronized (MyLock.a) {
System.out.println("女孩拿到了a");
System.out.println("女孩可以吃东西了");
}
}
}
}
public class MyLock extends Thread{
public static Object a = new Object();
public static Object b = new Object();
public static void main(String[] args) {
Boy boy = new Boy();
Girl girl = new Girl();
boy.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
girl.start();
}
}
线程通信
等待:
public final void wait()
public final void wait(long timeout)
必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait() 时,此线程
放其拥有的所有锁标记。同时此线程阻塞在o的等待队列中。释放锁,进入等待队列。
通知:
public final void notify()
public final void notifyAl1()
存钱取钱
Add
package Thread3;
public class Add implements Runnable{
private BankCard card;
//构造
public Add(BankCard card){
this.card = card;
}
@Override
public void run() {
for (int i = 0;i<10;i++){
card.save(1000);
}
}
}
Sub
package Thread3;
public class Sub implements Runnable{
private BankCard card;
public Sub (BankCard card){
this.card = card;
}
@Override
public void run() {
for (int i = 0;i<10;i++){
card.take(1000);
}
}
}
BankCard
package Thread3;
public class BankCard {
//余额
private double money;
//标记
private boolean flag = false;//ture 表示有钱可以取钱,false没钱 可以存取
//存钱
public synchronized void save(double m){//锁 this
if (flag) {//有钱
try {
this.wait();//进入等待队列,同事释放锁和cpu
} catch (InterruptedException e) {
e.printStackTrace();
}
}
money = money +m;
System.out.println(Thread.currentThread().getName()+"存了"+m+"余额是"+money);
//修改标记
flag = true;
//唤醒取钱线程
this.notify();
}
//取钱
public synchronized void take(double m ){
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
money = money - m;
System.out.println(Thread.currentThread().getName()+"取了"+m+"余额是"+money);
flag = false;
//唤醒存钱
this.notify();
}
}
Test
package Thread3;
public class TestBankCard {
public static void main(String[] args) {
//1创建银行卡
BankCard card = new BankCard();
//2创建操作
Add add = new Add(card);
Sub sub = new Sub(card);
//3创建线程对象
Thread chenchen = new Thread(add,"晨晨");
Thread bingbing = new Thread(sub,"冰冰");
//4启动
chenchen.start();
bingbing.start();
}
}
多存夺取问题分析
会紊乱
public class BankCard {
//余额
private double money;
//标记
private boolean flag = false;//ture 表示有钱可以取钱,false没钱 可以存取
//存钱
public synchronized void save(double m){//锁 this
while (flag) {//有钱,默认先是true?
try {
this.wait();//进入等待队列,同事释放锁和cpu
} catch (InterruptedException e) {
e.printStackTrace();
}
}
money = money +m;
System.out.println(Thread.currentThread().getName()+"存了"+m+"余额是"+money);
//修改标记
flag = true;
//唤醒取钱线程
this.notify();
}
//取钱
public synchronized void take(double m ){
while (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
money = money - m;
System.out.println(Thread.currentThread().getName()+"取了"+m+"余额是"+money);
flag = false;
//唤醒存钱
this.notify();
}
}
把if换成while,可以紊乱
但也可能四个线程都进入等待
把notify();改成notifyAll();
全部唤醒后,whlie,符合的进入循环
问题解决
生产者消费者
生产者、消费者:
若干个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个满的缓冲区中放入产品。
面包类
package Thread3;
public class Bread {
private int id ;
private String productName;
public Bread(){
}
public Bread(int id, String productName) {
this.id = id;
this.productName = productName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
@Override
public String toString() {
return "Bread{" +
"id=" + id +
", productName='" + productName + '\'' +
'}';
}
}
生产者类
package Thread3;
public class Product implements Runnable{
private BreadCon con;
public Product(BreadCon con){
super();
this.con = con;
}
@Override
public void run() {
for (int i = 0;i<30;i++){
con.input(new Bread(i,Thread.currentThread().getName()));
}
}
}
消费者类
package Thread3;
public class Consume implements Runnable{
private BreadCon con;
public Consume(BreadCon con){
super();
this.con = con;
}
@Override
public void run() {
for (int i = 0;i < 30;i++){
con.output();
}
}
}
面包容器类
package Thread3;
public class BreadCon {
//存放面包的数组
private Bread[] cons = new Bread[6];
//存放面包的位置
private int index = 0;
public synchronized void input(Bread b){//锁 this
//判断容器有没有满
while(index>=6){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
cons[index]=b;
System.out.println(Thread.currentThread().getName()+"生产了"+b.getId()+"");
index++;
this.notifyAll();
}
//取出面包
public synchronized void output(){//锁 this
while(index<=0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index--;
Bread b = cons[index];
System.out.println(Thread.currentThread().getName()+"消费了"+b.getId()+"生产者"+b.getProductName());
cons[index] = null;
//唤醒生产者
this.notifyAll();
}
}
测试类
package Thread3;
public class Test {
public static void main(String[] args) {
//容器
BreadCon con = new BreadCon();
//生产和消费
Product product = new Product(con);
Consume consume = new Consume(con);
//创建线程对象
Thread chenchen = new Thread(product,"晨晨");
Thread bingbing = new Thread(consume,"消费");
//启动线程
chenchen.start();
bingbing.start();
}
}