1.守护线程
线程分为守护线程和用户线程 : daemon thread and non-daemon thread
虚拟机必须等待用户线程执行完毕,但无需等待守护线程执行完毕。
守护线程举例:垃圾回收线程,监控内存线程。
setDaemon(true):默认为false,默认为用户线程
public class D19 {
public static void main(String[] args) {
Thread thread = new Thread(new A1());
thread.setDaemon(true);
thread.start();
new Thread(new A2()).start();
}
}
class A1 implements Runnable{
@Override
public void run() {
while(true){
System.out.println("This is a daemon thread.");
}
}
}
class A2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("This is a non-daemon thread.");
}
System.out.println("The non-daemon thread has finished.");
}
}
A1并没有设置停止条件,但因为将它设置为了守护线程,所以程序可以正常结束。
2.线程同步
并发:多个线程操作一个对象
当多个线程操作同一个对象,并且有线程想要修改该对象时,就会出现线程同步的问题。
线程同步是一种等待机制,多个线程进入该对象的等待池形成队列,等待前一个线程使用完毕,下一个线程再使用。
使用线程和锁来保证线程同步的安全性。
锁机制:当一个线程获得锁,独占资源,其他线程必须等待,直到该线程使用完毕,释放锁。
问题:
- 等待该资源的对象挂起
- 加锁,释放锁导致更多的开销:上下文切换,调度延时
- 优先级倒置
3.运用synchronized关键字解决线程并发造成的不安全问题
1)synchronized关键字修饰方法,默认锁的是该方法的对象
使用前:
import org.omg.CORBA.TIMEOUT;
public class D20 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
}
class Ticket implements Runnable{
int ticketNum = 20;
boolean flag = true;
@Override
public void run() {
while(flag){
try {
buy();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public void buy() throws InterruptedException {
if(ticketNum<=0){
flag = false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+" buy ticket: " + ticketNum--);
}
}
不安全结果:多个对象取到相同的票
加入synchronized关键字
import org.omg.CORBA.TIMEOUT;
public class D20 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
}
class Ticket implements Runnable{
int ticketNum = 20;
boolean flag = true;
@Override
public void run() {
while(flag){
try {
buy();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public synchronized void buy() throws InterruptedException {
if(ticketNum<=0){
flag = false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+" buy ticket: " + ticketNum--);
}
}
结果:不再出现取到同一张票的问题
2)用synchronized同步块
synchronized(Obj){ }
Obj为同步监视器,锁定代码块,同一时刻下只有一个线程访问该代码块
当synchronized修饰方法时,同步监视器默认为该方法的对象,也就是this
使用前:
import java.util.ArrayList;
import java.util.List;
public class D21 {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<String>();
for(int i=0;i<10000;i++){
new Thread(()->{
list.add(Thread.currentThread().getName());
} ).start();
}
Thread.sleep(100);
System.out.println(list.size());
}
}
不安全结果:多个线程向同一个地址写入数据,导致list的size不足10000
加入synchronized修饰代码块
import java.util.ArrayList;
import java.util.List;
public class D21 {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<String>();
for(int i=0;i<10000;i++){
new Thread(()->{
synchronized (list) {
list.add(Thread.currentThread().getName());
}
} ).start();
}
Thread.sleep(100);
System.out.println(list.size());
}
}
结果:
java中有线程安全的ArrayList: CopyOnWriteArrayList
import java.util.concurrent.CopyOnWriteArrayList;
public class D22 {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(
()->{
list.add(Thread.currentThread().getName());
}
).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(list.size());
}
}