Java语言基础
多线程
进程与线程
- 线程是依赖于进程存在的:
- 进程概述:
进程就是正在运行的程序,是系统进行资源分配和调用的独立单位;每一个进程都有它自己的内存空间和系统资源。
通过任务管理器我们就可以看到进程的存在。 - 多进程的意义:
单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),这正是人们所需的,所以我们常见的操作系统都是多进程操作系统。比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。
对于单核计算机来讲,游戏进程和音乐进程并不是同时运行的,CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。多进程的作用不是提高执行速度,而是提高CPU的使用率。
- 并行和并发:
并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。例如:你可以边吃饭边打电话, 这两件事情可以同时执行;
并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。例如:你吃一口饭喝一口水, 以正常速度来看, 完全能够看的出来, 当你把这个过程以n倍速度执行时…可以想象一下。 - Java程序运行原理:
Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程;该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法,所以 main方法运行在主线程中;JVM启动就是多线程的,至少启动了垃圾回收线程和主线程。 - 如何实现多线程:
由于线程是依赖进程而存在的,所以我们先创建一个进程出来,而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程;但Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序,Java可以去调用C/C++写好的程序来实现多线程程序,由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,然后提供一些类供我们使用,这样我们就可以实现多线程程序了;如Thread类。
1:初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
2:运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
3:阻塞(BLOCKED):表示线程阻塞于锁。
4:等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
5:超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
6:终止(TERMINATED):表示该线程已经执行完毕。
线程对象
- 实现线程:
- 多线程程序实现的方式1:继承Thread类
public class Demo extends Thread {
@Override
public void run() {
System.out.println("HelloWorld!");
}
public static void main(String[] args) {
Demo demo = new Demo();
demo.start();
}
}
/*run()和start()方法的区别:
我们启动线程使用不是run方法,而应该是start方法.使该线程开始执行;
Java 虚拟机调用该线程的 run 方法。
*/
- 多线程程序实现的方式2:实现Runnable接口
public class Demo implements Runnable {
@Override
public void run() {
System.out.println("HelloWorld!");
}
public static void main(String[] args) {
Demo demo = new Demo();
Thread thread = new Thread(demo);
thread.start();
}
}
- 多线程程序实现的方式2:实现Callable接口
前面两种线程定义方式都有这两个问题:
(1)无法获取子线程的返回值;
(2)run方法不可以抛出异常。
为了解决这两个问题,我们就需要用到Callable这个接口了。
public class Demo implements Callable {
@Override
public Object call() throws Exception {
int j=1;
System.out.println("HelloWorld!");
return j;//子线程的返回值
}
public static void main(String[] args) {
Demo demo = new Demo();
FutureTask futureTask = new FutureTask(demo);
Thread thread = new Thread(futureTask);
thread.start();
try {
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//使用 FutureTask 包装 Callable 对象。因为 FutureTask 实现了 Runnable,所以可将 FutureTask 提交给 Executor 执行。
- 线程命名:
所有的线程程序的执行,每一次都是不同的运行结果,如果要想区分每一个线程,那么就需要依靠线程的名字。对于线程的名字一般而言会在启动之前进程定义,不建议对已经启动的线程,进行更改名称,或者为不同线程设置重名的情况。
//继承Thread类的线程命名,默认名和命名
public class Demo extends Thread {
@Override
public void run() {
System.out.println("子线程名称:"+Thread.currentThread().getName());
}
public static void main(String[] args) {
Demo demo= new Demo();
demo.start();
System.out.println("主线程名称:"+Thread.currentThread().getName());
}
}
//主线程名称:main
//子线程名称:Thread-0
/**
*修改线程名字:
*/
public class Demo extends Thread {
public Demo(String name) {
//super(name);
this.setName(name);
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
Demo demo= new Demo("新名字");
demo.start();
System.out.println("线程名称:"+demo.getName());
}
}
//实现Runnable接口的线程命名
public class Demo2 implements Runnable {
@Override
public void run() {
System.out.println("线程名称:" + Thread.currentThread().getName());
}
public static void main(String[] args) {
Demo2 demo2 = new Demo2();
Thread thread = new Thread(demo2, "线程1");
System.out.println("线程名称:" + thread.getName());
thread.start();
}
}
- 线程方法:
- setPriority(int newPriority):更改线程的优先级;线程的优先级用数字表示,范围从1~10,Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5;
使用以下方式改变或获取优先级:getPriority(),setPriority(int xxx)
优先级的设定建议在start()调度前
public class Demo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("线程名称:"
+ Thread.currentThread().getName()
+ ",线程优先级:"
+ Thread.currentThread().getPriority()
);
}
}
public static void main(String[] args) {
Demo demo = new Demo();
Thread thread1 = new Thread(demo,"线程1");
thread1.setPriority(Thread.MIN_PRIORITY);
Thread thread2 = new Thread(demo,"线程2");
thread2.setPriority(Thread.MAX_PRIORITY);
Thread thread3 = new Thread(demo,"线程3");
thread3.setPriority(5);
thread1.start();
thread2.start();
thread3.start();
}
}
/*
线程名称:线程2,线程优先级:10
线程名称:线程2,线程优先级:10
线程名称:线程2,线程优先级:10
线程名称:线程3,线程优先级:5
线程名称:线程3,线程优先级:5
线程名称:线程3,线程优先级:5
线程名称:线程1,线程优先级:1
线程名称:线程1,线程优先级:1
线程名称:线程1,线程优先级:1
*/
- static void sleep(long millis):线程休眠,在指定的毫秒数内让当前正在执行的线程休眠
//线程休眠演示:每隔1秒在屏幕上输出一句诗
public class MyTest implements Runnable {
@Override
public void run() {
String s[]= {
"鹅!鹅!鹅!",
"曲项向天歌",
"白毛浮绿水",
"红掌拨清波"
};
for (int i = 0; i < s.length; i++) {
System.out.println(s[i]);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
MyTest myTest = new MyTest();
Thread thread = new Thread(myTest);
thread.start();
}
}
- void join():线程联合;待此线程执行完成后,再执行其他线程,其他线程阻塞,导致当前线程暂停执行,直到线程终止。
public class Demo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程" + i);
}
}
public static void main(String[] args) {
Demo demo = new Demo();
Thread thread = new Thread(demo);
thread.start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程main" + i);
if(i == 2) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
/*
主线程main0
主线程main1
主线程main2
子线程0
子线程1
子线程2
子线程3
子线程4
主线程main3
主线程main4
*/
- static void yield():线程礼让,暂停当前正在执行的线程对象,并执行其他线程
//模拟线程礼让:
public class MyTest implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始了");
Thread.yield();
System.out.println(Thread.currentThread().getName() + "结束了");
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
Thread thread1 = new Thread(myTest, "线程1");
Thread thread2 = new Thread(myTest, "线程2");
thread1.start();
thread2.start();
}
}
- 守护线程:
Java线程分为两种:用户线程和守护线程。
(1):用户线程可以认为是系统的工作线程,虚拟机必须确保用户线程执行完毕,完成这个程序要完成的业务员操作。
(2):守护线程是一种特殊的线程,就和它的名字一样,它是系统的守护者,在后台默默完成一些系统性的服务,比如垃圾回收线程、后台记录操作日志、监控内存;
如果用户线程全部结束,则意味着这个程序无事可做。守护线程要守护的对象已经不存在了,那么整个应用程序就结束了。 - “龟兔赛跑”:模拟线程
1、实例化两个线程
2、判断谁是胜利者,最终乌龟取胜
3、模拟兔子睡觉,每10米休息一次
public class Race implements Runnable{
private static String winner=null;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
//调整比赛速度
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//模拟兔子睡觉
if ("兔子".equals(Thread.currentThread().getName()) && i % 10 == 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
boolean flag=gameOver(i);
if (flag){
break;
}
System.out.println(Thread.currentThread().getName()+"跑了"+i+"米");
}
}
public boolean gameOver(int m){
if (winner!=null){
return true;
}
if (m>=100){
winner=Thread.currentThread().getName();
System.out.println("胜利者是:"+winner);
return true;
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
Thread thread = new Thread(race,"兔子");
Thread thread1 = new Thread(race, "乌龟");
thread.start();
thread1.start();
}
}
线程同步
- 引出:
处理多线程问题时,我们遇到线程冲突,这时候我们就需要线程同步;线程同步其实就是一种等待机制 , 多个需要同时访问 此对象的线程进入这个对象的等待池 形成队列, 等待前面线程使用完毕 , 下一个线程再使用。
线程冲突:当在不同线程中运行作用于相同数据的两个操作时,就会发生干扰。这意味着这两个操作由多个步骤组成,并且步骤顺序重叠。
- 同步用法:
Java编程语言提供了两种基本的同步习惯用法:同步语句(synchronized statements )和同步方法(synchronized methods )。
同步语句是创建同步代码的一种方法;
同步语句必须指定提供内部锁的对象。
- 同步语句
synchronized (Obj ) { }
Obj 称之为 :同步监视器
Obj 可以是任何对象 , 可以是this , 就是这个对象本身 、""等
模拟电影院三个售票员(一个售票员就是一个线程)卖票,总共100张票,打印出是哪个售票员卖了一张票并统计剩余几张票,我们会发现启动这三个线程会产生顺序错乱,不同的售票员卖了同一张票,还会出现负值的情况,此时就需要进行线程同步:
//使用同步语句将会出现问题的语句框起来
public class ThreadConflict implements Runnable {
// 票数100张
private static int ticket = 100;
@Override
public void run() {
while (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (""){
if (ticket<=0){
return;
}
System.out.println(Thread.currentThread().getName() + "卖了1张票,还剩" + --ticket + "张");
}
}
}
public static void main(String[] args) {
// 实例化子线程
ThreadConflict threadConflict = new ThreadConflict();
// 模拟三个售票员
Thread thread1 = new Thread(threadConflict, "售票员1");
Thread thread2 = new Thread(threadConflict, "售票员2");
Thread thread3 = new Thread(threadConflict, "售票员3");
thread1.start();
thread2.start();
thread3.start();
}
}
- 同步方法
synchronized方法控制对 “对象” 的访问 , 每个对象对应一把锁 , 每个 synchronized方法都必须获得调用该方法的对象的锁才能执行 , 否则线程会阻塞 , 方法一旦执行 , 就独占该锁 , 直到该方法返回才释放锁 , 后面被阻塞的线程才能获得这个锁 , 继续执行。
缺陷 : 若将一个大的方法申明为synchronized 将会影响效率
public class ThreadConflict2 implements Runnable {
// 票数100张
private static int ticket = 100;
@Override
public void run() {
while (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
sellTicket();
}
}
public synchronized void sellTicket(){
if (ticket<=0)
return;
System.out.println(Thread.currentThread().getName() + "卖了1张票,还剩" + --ticket + "张");
}
public static void main(String[] args) {
// 实例化子线程
ThreadConflict2 threadConflict = new ThreadConflict2();
// 模拟三个售票员
Thread thread1 = new Thread(threadConflict, "售票员1");
Thread thread2 = new Thread(threadConflict, "售票员2");
Thread thread3 = new Thread(threadConflict, "售票员3");
thread1.start();
thread2.start();
thread3.start();
}
}
//下篇再见…