Java多线程编程
Java虚拟机允许应用程序并发地运行多个执行线程,它们会交替运行,彼此之间可以进行通信。
每个线程都有一个优先级,这样有助于系统确定线程的调度顺序。
多线程并发执行可以提高程序效率,更加充分的使用CPU。
并行和并发
-
并行:就是同时执行,比如你边看电视边吃薯片。(需要多核CPU来实现)
-
并发:就是交替执行,比如你看会电视,然后吃会薯片,再看会电视,再吃会薯片......
指的是两个任务都在请求运行,但是处理器只能执行一个任务,就把这两个任务安排轮流执行,
由于时间间隔较短,使人感觉两个任务都在运行。
1.1 创建新线程的两种方式:
1.继承Thread 类重写run() 方法;
class MyThread extends Thread{
@Override
public void run() {
// To do anything you want.
}
}
调用:
MyThread thread = new MyThread();
thread.start();
2.实现Runnable接口重写run()方法;
class MyRunnable implements Runnable{
@Override
public void run() {
// To do anything you want.
}
}
调用:
MyRunnable runnable=new MyRunnable();
new Thread(runnable).start();
注意最后都是通过 Start() 方法来开启线程。
1.2 两种方式区别:
继承Thread:
好处:可以直接使用Thread中方法,代码简洁。
弊端:如果已经有了父类,则不能使用这种方式。
实现Runnable接口:
好处:当线程类中已经有了父类时可以使用。
弊端:不能直接使用Thread中的方法,需要先获取到线程对象,才能使用,代码复杂。
使用时需优先考虑Thread
2. Thread 常用方法:
方法名 | 描述 |
---|---|
currentThread() | 返回当前执行的线程对象 |
getName() | 返回该线程的名称 |
setName(String name) | 设置线程名称 |
sleep(long millis) | 使线程休眠(暂停执行),参数为毫秒 |
start() | 使线程开始执行;Java虚拟机调用该线程的run()方法 |
setPriority(int priority) | 更改线程优先级 |
setDaemon(bolean on) | 将该线程标记为守护线程或用户线程 |
isDaemon() | 判断线程是为守护线程 |
isAlive() | 判断当前线程是否为活动状态 |
join() | 让主线程等待子线程结束后再继续运行(会阻塞主线程) |
yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
3.守护线程
Java线程中有两种线程: User Thread(用户线程), Daemon Thread(守护线程,也叫后台线程)
新创建的线程默认都是前台线程,只有当创建线程为守护线程时,新线程才是守护线程。
当所有用户线程都退出后,守护线程也会退出。(举个例子来说的话就是:当你关闭浏览器后,所有的网页都会关闭)
可以通过 setDaemon(true)
改变线程为守护线程,注意该方法必须在start()方法之前调用。
4.线程的优先级
每个Java线程都有有一个优先级,默认优先级是NORM_PRIORITY(5)。
优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程。
设置优先级有助于操作系统确定线程的调度顺序,但是不能保证线程执行的顺序,也就是说高优先级的线程不一定先执行完。
当某个线程中创建一个新的Thread对象,该新线程的初始优先级被设定为创建线程的优先级。
JDK预定义优先值为:
public final static int MIN_PRIORITY = 1; //The minimum priority that a thread can have.
public final static int NORM_PRIORITY = 5; //The default priority that is assigned to a thread.
public final static int MAX_PRIORITY = 10; //The maximum priority that a thread can have.
设置优先级使用setPriority()方法,该方法源码为:
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
synchronized(this) {
this.priority = newPriority;
if (isAlive()) {
nativeSetPriority(newPriority);
}
}
}
}
根据源码我们可以得出,优先级取值范围为1~10,如果小于1或者大于10则将抛出异常。
5.线程同步
多线程开发时,当有多段代码需要执行,我们希望某一段代码执行的过程中CPU不要切换到其他线程
这就需要用到同步代码块,这样的话只有当同步代码块执行结束后才会执行其它代码。
通过关键字 synchronized
来实现 ,格式:
//任意对象都可以当做锁对象
//注意:匿名对象不能当做锁对象,因为不能保证两个锁对象是同一个对象
synchronized(lock){ //获取锁
//需要同步的代码块;
} //释放锁
除了代码块可以同步外,方法也是可以同步的。
声明格式 :
synchronized void 方法名称(){}
同步方法虽然没有声明锁对象,但是它也是有锁对象的:
非静态同步方法:this 静态同步方法:类名.class (就是当前类的字节码对象)
锁对象,它是同步代码块的关键。当线程执行同步代码块时,首先会检查锁对象的标志位,默认标志位为1,此时线程会执行同步代码块,同时将锁对象标志位置为0。当一个新的线程执行到这段代码块是,由于锁对象的标志位为0,新线程会发生阻塞,等待当前线程执行完同步代码块后,锁对象的标志位被置为1,新线程才能进入同步代码块执行其中的代码。循环往复,知道共享资源被处理完位为止。这个过程就好比一个公用电话亭,只有前一个人都打完电话出来后,后面的人才可以打。
注意:同步代码块中锁对象可以使任意类型的对象,但是多个线程共享的锁对象必须是唯一的。
6.死锁
死锁就是两个或多个线程被永久阻塞是的一种情形。
假设这样一场景,小明去买冰棍:
售货员:“你先给我钱,我再给你冰棍”
小明:“你先给我冰棍,我再给你钱”
最终的结果就是小明买不到冰棍,售货员也得不到钱。
在这里钱跟冰棍就是锁,两个线程在运行时都在等待对方的锁,这样就造成的阻塞,这种现象称为死锁。
接下来通过代码来模拟:
class DeadLockThread implements Runnable {
static Object ice = new Object(); // 锁对象:冰棍
static Object money = new Object(); // 锁对象:钱
private boolean flag; // true:小明 false:售货员
public DeadLockThread(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) { //小明
while (true) {
synchronized (money) {
System.out.println("小明: 给我冰棍");
synchronized (ice) {
System.out.println("售货员: 给你冰棍");
}
}
}
} else { //售货员
while (true) {
synchronized (ice) {
System.out.println("售货员: 给我钱");
synchronized (money) {
System.out.println("小明: 给你钱");
}
}
}
}
}
}
创建两个线程:
DeadLockThread t1=new DeadLockThread(true); //小明
DeadLockThread t2=new DeadLockThread(false); //售货员
//创建并开启两个线程
new Thread(t1).start();
new Thread(t2).start();
输出结果:
小明: 给我冰棍
售货员: 给我钱
最终小明锁着”钱”,售货员锁着”冰棍”,小明得不到冰棍,售货员得不到钱。
在开发中我们是尽量死锁的,方式之一就是尽量不要使用同步代码块的嵌套。
7.单例设计模式
目的:保证类在内存当中只有一个对象。
两种写法:
//饿汉式
class Singleton{
//1.私有构造函数
private Singleton() {}
//2.创建本类对象
private static Singleton s=new Singleton();
//3.对外提供公共的访问方法
public static Singleton getInstance(){
return s;
}
}
//懒汉式
class Singleton{
//1.私有构造函数
private Singleton() {}
//2.创建本类对象
private static Singleton s;
//3.对外提供公共的访问方法
public static Singleton getInstance(){
if(s==null)
s=new Singleton();
return s;
}
}