一、线程
线程依赖于进程
1、进程
进程就是正在运行的程序,是系统资源进行资源分配和调用的基本单位。在某一时刻,可以运行好几个进程;但在某个具体时间点上CPU(单核)只能执行一个进程。因为CPU可以在多个进程之间进行一个高速的转换,所以人们是感觉不出来的。多进程的意义,在于提高CPU的利用率。
2.线程
在一个进程内部又可以执行多个任务,而这每一个任务我们都可以看成是一个线程。是程序使用CPU的基本单位。所以,程序是拥有资源的基本单位,线程是CPU调度的基本单位。
- 线程依赖于进程。进程开启后,他会执行很多的任务,那么每个任务,我们就称之为线程。
- 线程具有随机性,它会抢占CPU的执行权。谁抢到,CPU就会在某一时刻执行谁。
- 并发:多个任务交替执行,只是交替的间隔很短,让你感觉它在同时进行
- 并行:多个任务同时进行
- JVM多线程:是至少有两个线程。一个线程是主线程,还有一个垃圾回收线程
二、多线程程序的实现方式
1.多线程程序的实现方式1
我们创建线程的方式1
1.我们定义一个类,继承Thread这个类
2.重写Thread这个类中的run方法
3.创建我们写的这个类的对象
4.开启线程
public class MyThread extends Thread{
@Override
public void run() {
//这个run()方法就是需要线程来执行的代码,一般耗时的操作,我们会放到run()方法里面,由线程去执行
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
public class MyTest {
public static void main(String[] args) {
MyThread th = new MyThread();
th.start();
//调用start()开启线程,由线程去调用run()去执行run()里面的方法
MyThread th2 = new MyThread();
th2.start();
//同一个线程不要多次开启,会抛异常
}
}
2.多线程程序的实现方式2
开启线程的方式2:
1.创建一个类,实现Runable接口,重写该接口中的run方法
2.创建Thread类对象,将Runable接口的子类对象传进来
3.调用start()方法开启线程
public class MyRunable implements Runnable {
@Override
public void run() {
//需要线程执行
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
public class MyTest {
public static void main(String[] args) {
//Runnable接口应该由那些打算通过某一个线程执行其实例的类实现
MyRunable myRunable = new MyRunable();
Thread th = new Thread(myRunable);
th.start();
}
}
3.多线程程序的实现方式3
开启线程的方式3:
1.创建一个类实现Callable接口
2.创建一个FutureTask类将Callable接口的子类对象作为参数传递进去
3.创建Thread类,将FutureTask对象作为参数传进去
4.开启线程
public class MyCallable implements Callable<Integer> {
//call()方法就是线程要执行的方法
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 0; i <= 100; i++) {
sum+=i;
}
return sum;
}
}
public class MyTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建进程的方式3
MyCallable myCallable = new MyCallable();
FutureTask<Integer> task = new FutureTask<Integer>(myCallable);
Thread th= new Thread(task);
th.start();
//线程执行完之后,可以获取结果
Integer integer = task.get();
System.out.println(integer);
}
}
三、多线程的属性和基本方法
1.获取和设置线程对象的名称
public static void main(String[] args) {
//获取主线程的名称
Thread thread = Thread.currentThread();//获取当前线程对象
String name = thread.getName();
System.out.println(name);
MyThread th = new MyThread();
th.start();
//设置线程名称
th.setName("小祖宗");
}
public class MyThread extends Thread{
@Override
public void run() {
//获取线程名称
//String name = this.getName();
String name = Thread.currentThread().getName();
for (int i = 0; i < 100; i++) {
System.out.println(name+i);
}
}
}
2.获取和设置线程的优先级
线程有两种调度模型:
- 分时调度模型:所有线程轮流使用CPU的使用权,平均分配给每个线程占用CPU的时间片
- 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个
//java使用线程的调度模型,是抢占式调度
//如果说多个线程的优先级一样,线程的执行,就是随机抢占
MyThread th1 = new MyThread();
MyThread th2 = new MyThread();
//设置线程的优先级
th1.setPriority(Thread.MAX_PRIORITY);
th2.setPriority(Thread.MIN_PRIORITY);
//获取线程的优先级
th1.getPriority();
th2.getPriority();//线程的优先级1-10,默认优先级是5
th1.start();
th2.start();
3.线程控制之休眠线程
public static void main(String[] args) throws InterruptedException {
//通过构造,给线程设置名字
MyThread th1 = new MyThread("张飞");
th1.start();
th1.sleep(3000);//让当前线程休眠
}
public class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
@Override
public void run() {
//让线程睡一会,就是让线程处于一种堵塞状态
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
4.线程控制之加入线程
意思就是:等待该线程执行完毕了以后,其他线程才能再次执行
public static void main(String[] args) throws InterruptedException {
MyThread th1 = new MyThread("张飞");
MyThread th2 = new MyThread("刘备");
MyThread th3 = new MyThread("关羽");
//join()在线程开启之后,去调用
th1.start();
th1.join();
th2.start();
th1.join();
th3.start();
th1.join();//串行:多个线程按顺序执行
}
5.线程控制之礼让线程
public static void main(String[] args) {
MyThread th1 = new MyThread("张飞");
MyThread th2 = new MyThread("刘备");
th1.start();
th2.start();
}
public class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
Thread.yield();//暂停当前正在执行的线程对象,并执行其他线程
System.out.println(i);
}
}
}
6.线程控制之守护线程
用户线程和守护线程都是线程,区别就是Java虚拟机在所有用户线程dead后,程序就会结束。而不管是否还有守护线程还在运行,若守护线程还在运行,则会马上停止。
public static void main(String[] args) {
//主线程也称为用户线程
Thread.currentThread().setName("刘备");
MyThread th1 = new MyThread("张飞");
MyThread th2 = new MyThread("关羽");
th1.setDaemon(true);
th2.setDaemon(true);
//在线程开启之前,可以设为守护线程
th1.start();
th2.start();
}
7.线程控制之中断线程
public static void main(String[] args) throws InterruptedException {
MyThread th = new MyThread("张飞");
th.start();
th.stop();//强行停止线程的运行
Thread.sleep(1000);
th.interrupt();//打断线程的一个堵塞状态
}
8.多线程复制文件
public class MyThread1 extends Thread{
//复制第一个文件
@Override
public void run() {
try {
Files.copy(Paths.get("曾经的你.mp3"), Paths.get("曾经的你2.mp3"), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class MyThread2 extends Thread{
//复制第二个文件
@Override
public void run() {
try {
Files.copy(Paths.get("许巍 - 曾经的你.mp3"), Paths.get("许巍 - 曾经的你2.mp3"), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
//一个线程复制一个文件
MyThread1 th1 = new MyThread1();
MyThread2 th2 = new MyThread2();
th1.start();
th2.start();
}
四、小案例
需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
- 继承Thread类的方式
public class Test1 {
public static void main(String[] args) {
MyThread th1 = new MyThread();
MyThread th2 = new MyThread();
MyThread th3 = new MyThread();
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
public class MyThread extends Thread {
static int num=100;
@Override
public void run() {
while(num>=1){
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖第"+(num--)+"张票");
}
}
}
- 实现Runable接口的方式
public class Test2 {
public static void main(String[] args) {
MyRunable myRunable = new MyRunable();
Thread th1 = new Thread(myRunable);
Thread th2 = new Thread(myRunable);
Thread th3 = new Thread(myRunable);
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
public class MyRunable implements Runnable {
int num=100;
@Override
public void run() {
while(num>=1){
String name = Thread.currentThread().getName();
System.out.println(name+"正在售卖第"+(num--)+"张票");
}
}
}
五、线程安全、锁
1.线程安全问题
在多线程环境下;存在共享数据;有多条语句操作共享数据;这种情况下,有可能出现线程安全问题
2.同步代码块解决线程安全
- 同步代码块的格式:
synchronized(对象){
需要同步的代码;
}
public class MyRunable implements Runnable {
int num=100;
Object obj=new Object();
@Override
public void run() {
synchronized (obj){//锁,其实就是一个任意对象,多个线程要共享一把锁
while(num>=1){
try {
Thread.sleep(3000);//使用休眠来模拟网路延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name+"正在售卖第"+(num--)+"张票");
}
}
//释放锁
}
}
- 同步代码块的好处和弊端
好处:解决了多线程的安全问题
弊端:当线程较多时,因为每个线程都会去判断同步上的锁,这很耗费资源也降低了程序的运行效率 - 同步方法
private synchronized void sell() {//方法上加一个synchronized关键字,我们叫做同步方法
//同步方法所使用的对象不是任意对象,它用的锁是this
while(num>=1){
try {
Thread.sleep(3000);//使用休眠来模拟网路延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name+"正在售卖第"+(num--)+"张票");
}
}
- 静态同步方法
//静态同步方法使用的锁对象,不是任意对象,她用的锁是当前类的字节码类型
private synchronized static void sellMethod() {
while(num>=1){
try {
Thread.sleep(3000);//使用休眠来模拟网路延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name+"正在售卖第"+(num--)+"张票");
}
}
}
- JDK1.5之后的Lock锁
public class MyRunable implements Runnable {
static int num=100;
ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
//加锁
try {
lock.lock();
while(num>=1){
try {
Thread.sleep(3000);//使用休眠来模拟网路延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name+"正在售卖第"+(num--)+"张票");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//确保锁要释放掉
lock.unlock();
}
}
}
3.死锁
- 如果出现了同步嵌套,就很容易产生死锁问题
- 是指两个或者两个以上的线程,在抢占CPU的执行权的时候,都处于一种互相等待状态
public class MyTest {
public static void main(String[] args) {
MyThread th1 = new MyThread(true);
MyThread th2 = new MyThread(false);
th1.start();
th2.start();
}
}
public class MyThread extends Thread {
boolean flag;
public MyThread(boolean flag) {
this.flag=flag;
}
@Override
public void run() {
if(flag){
synchronized (ObjectUtils.objA){
System.out.println("objA执行了");
synchronized (ObjectUtils.objB){
System.out.println("objB执行了");
}
}//objA不释放
}else{
synchronized (ObjectUtils.objB){
System.out.println("objB执行了");
synchronized (ObjectUtils.objA){
System.out.println("objA执行了");
}
}
}//objB不释放
}
}
public interface ObjectUtils {
//创建两把锁对象
public static final Object objA=new Object();
Object objB=new Object();
}