一:java中实现多线程的常用方式
1、继承Thread类
public class Test1Thread {
/*
*
- 继承Thread类实现多线程的步骤(java.lang.Thread)
(1)声明类并继承Thread类
(2)重写Thread类的run方法
(3)创建定义的线程类的对象
(4)通过调用start方法,启动线程。
*/
public static void main(String[] args) {
test1();
}
private static void test1() {
//3、创建定义的线程类对象
Thread t1 = new WatchMovie();
//4、通过调用start方法,启动线程。
t1.start();
//再启动发送弹幕的线程
Thread t2 = new DanMu();
//启动t2线程
t2.start();
//main方法也是个线程:main线程
for(int i=0;i<=20;i++) {
System.out.println("这是main线程:执行"+i+"次");
}
}
}
//继承Thread类实现多线程的步骤(java.lang.Thread)
//(1)声明类并继承Thread类
class WatchMovie extends Thread{
//(2)重写Thread类的run方法:线程启动时会默认调用run方法。
@Override
public void run() {
//循环播放视频20次
for(int i=1;i<=20;i++) {
System.out.println("播放视频"+i+"次");
try {
//每打印一次,让线程休眠1秒钟
Thread.sleep(1000);//单位为毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//发送弹幕
//1、继承Thread类
class DanMu extends Thread{
//2、重写run方法
@Override
public void run() {
//循环发送20次弹幕
for(int i=1;i<=20;i++) {
System.out.println("发送弹幕"+i+"次");
//休眠1秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
2、实现Runnable接口
public class Test2Runnable {
/*
* - 实现Runnable接口实现多线程的步骤(java.lang.Runnable)
(1)声明类并实现Runnable接口
(2)重写Runnable接口的run方法
(3)创建实现Runnable接口类的实例对象
(4)绑定线程(通过创建Thread类的实例对象实现线程绑定)
(5)调用start方法,启动线程。
*/
public static void main(String[] args) {
test1();
}
private static void test1() {
//3)创建实现Runnable接口类的实例对象
Runnable r1 = new PlayGame();
//4、绑定线程(通过创建Thread类的实例对象实现线程绑定)
Thread t1 = new Thread(r1);
//(5)调用start方法,启动线程。
t1.start();
//创建对象
Runnable r2 = new BGM();
//绑定线程
Thread t2 = new Thread(r2);
//启动线程
t2.start();
}
}
//(1)声明类并实现Runnable接口
class PlayGame implements Runnable{
//2)重写Runnable接口的run方法
@Override
public void run() {
for(int i=1;i<=10;i++) {
System.out.println("打游戏"+i+"分钟");
//休眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//播放游戏背景音乐
class BGM implements Runnable{
//2)重写Runnable接口的run方法
@Override
public void run() {
for(int i=1;i<=10;i++) {
System.out.println("播放背景音乐"+i+"分钟");
//休眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
3匿名内部类创建线程
// 实现runnable借口,创建多线程并启动
new Thread(new Runnable() {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + "--"
+ x);
}
}
}) {
}.start();
二:线程的优先级
优先级指的是线程被CPU调度的概率,优先级越高,线程越容易被执行
线程的优先级有十个级别:分别从1-10(10表示优先级最高,1表示优先级最低)
线程默认优先级为5
设置线程优先级的方法:
setPriority(int newPriority);
优先级的常量数字
public static final int MAX_PRIORITY 表示10
public static final int MIN_PRIORITY 表示1
public static final int NORM_PRIORITY 表示5
三:守护线程
守护线程一般是后台线程:执行的是支持(伴随)业务逻辑的线程;比如gc线程
如果进程中有多个普通线程和守护线程同时存在,当所有普通线程执行完成以后,那么正在执行的守护线程也会随之结束
设置守护线程:
setDaemon(true)
四:线程同步
当多个线程访问同一对象时,可以利用线程同步使其他线程等待当前线程执行完成
同步方法(synchronized):
获取的对象锁是调用该同步方法的对象(即锁住调用该同步方法的对象)
同步块(synchronized):可以传入需要锁的对象obj(比较灵活)
synchronized关键字会自动获取和释放对象锁
对象锁:执行同步代码时会锁住对象每个对象都有一个对象锁,线程在执行synchronized修饰的代码时,会先自动获取对象
锁,阻止其他线程访问该对象,执行完synchronized(同步)的代码后,会自动释放对象锁。
public class Test4Sync {
/*
线程同步
现实生活中,我们经常会遇到“同一个资源,多个人都想使用”的问题。
这个的问题同样存在于Java当中,针对这一问题,Java引入了线程同步的概念:当多个线程访问同一对象时,可以利用线程同步使其他线程等待当前线程执行完成。
例如:你和你老婆共用同一个银行账户,某一天你俩刚好都要取钱:
现在账户里面只有1000块钱,你老婆需要取出600块买包,你需要取出600块钱洗脚。你俩心想钱是不是都够自己所需啊。
线程同步:由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题。
Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问造成的这种问题。
由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized方法和 synchronized 块。
- 同步方法(synchronized):获取的对象锁是调用该同步方法的对象(即锁住调用该同步方法的对象)。
- 语法:public [synchronized] void 方法名(){}
- 同步块(synchronized):可以传入需要锁的对象obj(比较灵活)
- synchronized (被锁的对象obj){}
- 对象锁:执行同步代码时会锁住对象
- 每个对象都有一个对象锁,线程在执行synchronized修饰的代码时,会先自动获取对象锁,阻止其他线程访问该对象;
执行完synchronized(同步)的代码后,会自动释放对象锁。
- synchronized关键字会自动获取和释放对象锁。
*/
public static void main(String[] args) {
test1();
}
private static void test1() {
//创建一个银行账户
Account accout = new Account("你和你老婆的账户", 1000);
//创建你的线程
Runnable r1 = new GetMoney(accout,600);
//绑定线程
Thread t1 = new Thread(r1,"你");
//创建你老婆的线程对象
Runnable r2 = new GetMoney(accout,600);
Thread t2 = new Thread(r2,"你老婆");
t2.setPriority(8);
//启动线程
t1.start();
t2.start();
}
}
/*
* 例如:你和你老婆共用同一个银行账户,某一天你俩刚好都要取钱:
现在账户里面只有1000块钱,你老婆需要取出600块买包,你需要取出600块钱洗脚。你俩心想钱是不是都够自己所需啊。
*/
//定义银行账户
class Account{
private String name;//账户名
private int money;//账户的钱
//把取钱的功能定义在账户里面,这样就可以获取账户的对象锁
public synchronized void getMoney(int money) {//money:表示准备取的钱
//取钱:先判断账户余额是否充足
if(money>this.getMoney()) {
//账户余额不足
System.out.println("账户余额不足,请及时充值。。。。");
return;//结束方法
}
//让线程休眠2秒钟
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//正常取钱
//this.setMoney(this.getMoney()-money);
this.money = this.money - money;
//打印取钱成功的信息
System.out.println("恭喜"+Thread.currentThread().getName()+"取钱成功,一共取了"+money+"元,账户余额围为:"+this.getMoney()+"元。");
}
public Account(String name, int money) {
super();
this.name = name;
this.money = money;
}
public Account() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
//取钱的任务
class GetMoney implements Runnable{
//定义账户
Account account;
//取钱的数目
int money;
//重写run方法
@Override
public void run() {
//打印谁要取钱
System.out.println(Thread.currentThread().getName()+"准备去取钱。。。。。");
System.out.println(Thread.currentThread().getName()+"准备取"+money+"元。。。");
System.out.println(Thread.currentThread().getName()+"开始取钱。。。。");
//开始取钱
//this.getMoney();
//调用账户取钱的功能
//account.getMoney(600);//可以同步,但是有点麻烦
//推荐使用 - 同步块(synchronized):可以传入需要锁的对象obj(比较灵活)
synchronized (account) {
//取钱:先判断账户余额是否充足
if(money>account.getMoney()) {
//账户余额不足
System.out.println("账户余额不足,请及时充值。。。。");
return;//结束方法
}
//让线程休眠2秒钟
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//正常取钱
account.setMoney(account.getMoney()-money);
//打印取钱成功的信息
}
System.out.println("恭喜"+Thread.currentThread().getName()+"取钱成功,一共取了"+money+"元,账户余额围为:"+account.getMoney()+"元。");
}
//定义取钱的功能:这样定义功能,会造成资源访问冲突的问题,造成账户余额异常,出现负数。 - 需要使用线程同步来解决
/*public void getMoney() {
//取钱:先判断账户余额是否充足
if(money>account.getMoney()) {
//账户余额不足
System.out.println("账户余额不足,请及时充值。。。。");
return;//结束方法
}
//让线程休眠2秒钟
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//正常取钱
account.setMoney(account.getMoney()-money);
//打印取钱成功的信息
System.out.println("恭喜"+Thread.currentThread().getName()+"取钱成功,一共取了"+money+"元,账户余额围为:"+account.getMoney()+"元。");
}*/
//定义为同步方法:synchronized表示同步 - 也不能解决问题,获取的对象锁不对。
/*public synchronized void getMoney() {//获取的对象锁:是调用该同步方法的对象的锁(也即是GetMoney的对象),不是account的对象。
//取钱:先判断账户余额是否充足
if(money>account.getMoney()) {
//账户余额不足
System.out.println("账户余额不足,请及时充值。。。。");
return;//结束方法
}
//让线程休眠2秒钟
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//正常取钱
account.setMoney(account.getMoney()-money);
//打印取钱成功的信息
System.out.println("恭喜"+Thread.currentThread().getName()+"取钱成功,一共取了"+money+"元,账户余额围为:"+account.getMoney()+"元。");
}*/
public GetMoney(Account account, int money) {
super();
this.account = account;
this.money = money;
}
public GetMoney() {
super();
}
}
五:死锁
线程死锁:两个线程同时等待对方释放资源,但是自己没有释放资源,这就会造成线程死锁
线程死锁一般是由于逻辑问题造成的,通常都是由于同步的嵌套使用
如何解决线程死锁的问题:
制定合理的业务逻辑:比如说规定抢占资源的顺序