疑问:windows号称是多任务的操作系统 , widnows真的在同时运行多个应用程序吗?
宏观角度,windows确实在同时运行多个应用程序。
微观角度, cpu在做一个快速的切换动作,由于切换的速度比较快,所以我们没有感觉到而已.
进程: 正在运行的程序称作为一个进程。 进程负责了内存空间的划分。
线程: 线程负责了代码的执行, 线程是进程中的一个代码执行路径。
多线程 : 在一个进程中有多个线程在执行不同的任务代码。
疑问: 以前我们没有学过线程啊,为什么代码还是可以执行呢?
任何一个java程序在运行的时候,jvm都会为该应用程序创建一个主线程, 主线程的任务就是把main方法的代码执行完毕。
笔试题目: 一个java应用程序在运行的时候至少有几个线程?
2个线程, 主线线程, 垃圾回收器线程。
多线程的好处:
1. 解决在一个进程中可以同时执行多个任务代码的问题。
2. 提高了资源利用率。
多线的缺点:
1. 增加了cpu的负担。
2. 降低了一个进程中线程的执行概率.
3. 引发了线程安全问题。
4. 引发了死锁现象.
如何自定义线程:
1. 自定义一个类继承Thread。
2. 重写Thread类的run方法, 把自定义线程的任务代码定义在run方法上。
疑问: 重写run方法的目的是什么?
每个线程都有自己的任务代码, main线程的任务代码是main方法里面的所有代码, 而自定义线程的任务代码就是run方法中的所有代码。
3. 创建自定义线程对象。
4. 调用线程的start方法开启线程, 一个线程一旦开启就会执行run方法中的所有代码。
注意: run方法千万不能直接调用,直接调用run方法相当于调用了一个普通的方法而已,并没有开启一个新的线程。
public class Demo1 extends Thread {
@Override
public void run() {
//自定义线程的任务代码
for(int i = 0 ; i< 100 ; i++){
System.out.println("自定义线程:"+i);
}
}
//每个线程都有自己的任务代码.....
public static void main(String[] args) {
//创建线程对象
Demo1 d = new Demo1();
//开启线程
d.start();
for(int i = 0 ; i< 100 ; i++){
System.out.println("主线程:"+i);
}
}
}
线程常用的方法:
Thread(String name) 初始化线程的名字
setName(String name) 设置线程对象名
getName() 返回线程的名字
static sleep() 那个线程执行了sleep的代码 ,那么该线程就会睡眠指定毫秒数。
currentThread() 返回当前执行该方法的线程对象引用。
getPriority() 返回当前线程对象的优先级 默认线程的优先级是5
setPriority(int newPriority) 设置线程的优先级 虽然设置了线程的优先级,但是具体的实现取决于底层的操作系统的实现(最大的优先级是10 ,最小的1 , 默认是5)。
public class Demo3 extends Thread {
public Demo3(String name){
super(name);// 指定调用Thread类一个参数的构造方法。给线程初始化名字。
}
@Override
public void run() {
for(int i = 0 ; i<100 ; i++){ System.out.println(Thread.currentThread().getName()+":"+i);
} //System.out.println(Thread.currentThread()==this);
}
public static void main(String[] args) throws Exception {
//创建一个自定义的线程对象
Demo3 d = new Demo3("狗娃");
d.setPriority(1);
d.start();
System.out.println("自定义线程的优先级:"+ d.getPriority());
// Thread.sleep(1000); 指定线程睡眠的毫秒数
Thread mainThread = Thread.currentThread(); // 返回当前线程.
System.out.println("主线程的优先级:"+ mainThread.getPriority()); //默认的优先级是5 .
mainThread.setPriority(10); //设置线程的优先级 优先级越高的线程得到cpu的概率越大。 优先级的范围:1~10
System.out.println("主线程的名字:"+ mainThread.getName());
for(int i = 0 ; i<100 ; i++){ System.out.println(mainThread.getName()+":"+i);
}
}
}
需求: 模拟车站卖50张票。
问题1:50张票被卖了150次 ?
原因:num是非静态的成员变量,非静态的成员变量每创建一个SaleTickets对象的时候,都会在内部维护一份num数据,这时候创建了三个SaleTickets对象,所以就有三份num数据。
解决方案: 使用static修饰票数num,让该数据共享出来给所有的对象使用。
问题2: 出现了线程安全问题。
线程安全问题出现的根本原因:
1. 存在着两个或者两个以上的线程。
2. 多个线程共享了着一个资源,而且操作资源的代码有多句。
线程安全问题的解决方案:
1. 使用同步代码块
格式:
synchronized(锁对象){
需要被同步的代码;
}
同步代码块要注意的细节:
1. 锁对象可以是任意的对象、.
2. 锁对象必须是多个线程共享的对象(锁对象必须是唯一)。
3. 线程调用了sleep方法是不会释放锁对象的。
4. 只有会出现线程安全问题的时候才使用java的同步机制(同步代码块和同步 函数),,
练习: 模拟两夫妻去银行取钱, 一个拿着卡,一个拿着存折 , 每次取100块, 总额是5000块。 不准出现线程安全问题。
2. 同步函数
class SaleTickets extends Thread{
static int num = 50; //非静态成员变量。 非静态成员变量在每个对象中都维护了一份数据。
static Object o = new Object();
public SaleTickets(String name){
super(name); //调用父类一个参数的构造函数, 初始化线程的名字。
}
//线程的任务代码...
@Override
public void run() {
while(true){
synchronized ("锁") {
if(num>0){
try {
Thread.sleep(100); System.out.println(Thread.currentThread().getName()+"卖出了"+num+"号票");
num--;
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
System.out.println("售罄了...");
break;
}
}
}
}
}
public class Demo4 {
public static void main(String[] args) {
//创建线程对象
SaleTickets thread1 = new SaleTickets("窗口1");
SaleTickets thread2 = new SaleTickets("窗口2");
SaleTickets thread3 = new SaleTickets("窗口3");
//开启线程
thread1.start();
thread2.start();
thread3.start();
}
}
多线程好处:
1. 解决了在一个进程中 可以同时执行多个任务代码的问题。
2. 提高资源的利用率
多线程的弊端:
1. 增加了cpu的负担
2. 降低了一个进程中线程的执行概率。
3. 引发了线程安全问题。
4. 引发了死锁现象。
自定义线程的步骤:
1. 自定义一个类继承Thread
2. 重写run方法,把自定义线程的任务定义在run方法中。
3. 创建Thread子类的对象,然后调用start方法开启线程。
注意: 千万不要直接调用run方法,直接调用run相当于调用了一个普通的方法而已,并没有开启一个新的线程。
线程的生命周期状态图
sleep\wait()
<————-临时阻塞<————-
调用了start方法 抢夺了cpu的执行权 执行完任务
创建 ———————> 可运行 —————————–>运行 ————–>消亡
<—————————
被抢夺了cpu的执行权
线程安全问题出现的根本原因:
1. 必须存在两个或者两个以上的线程。
2. 多个线程共享着一个资源,而且操作资源的代码有多句。
出现了线程安全问题的解决:
1. 同步代码块
格式:
synchronized(锁对象){
需要被同步的代码
}
同步代码块要注意的事项:
1. 锁对象可以是任意的一个对象。
2. 锁对象必须是多个线程共享 的资源。
3. 调用了sleep方法的线程并不会释放锁对象。
4. 如果不存在着线程安全问题,千万不要使用同步代码块或者是同步函数, 因为会降低效率的。
2. 同步函数 : 使用synchronized修饰该函数则称作为同步函数。
同步函数要注意的事项:
1. 非静态同步函数的锁对象是this对象,静态函数的锁对象是当前所属类的class文件对象。
2. 同步函数的锁对象是固定的,无法更改。
推荐使用: 同步代码块
推荐的原因:
1. 同步代码块的锁对象可以由我们自己指定,同步函数的锁对象是固定 的。
2. 同步代码块可以随意指定那个范围需要被同步,而同步函数必须是整个函数都同步, 代码不灵活。
练习: 夫妻俩一起去银行取钱, 一个拿着存折, 一个拿着卡,同时取钱。 每次取100块,账户的总额是5000块,不准出现 线程安全问题。
class BankThread extends Thread{
static int count = 5000; //账户的总额
public BankThread(String name){
super(name);
}
//非静态静态的同步函数 ----- 锁对象this对象。
@Override
public void run() {
getMoney();
}
//静态的同步函数 ---------->当前方法所属的类的class文件对象。
public synchronized static void getMoney(){
while(true){
if(count>0){ System.out.println(Thread.currentThread().getName()+"取走了100块,还剩余"+(count-100)+"块");
count-=100;
}else{
System.out.println("取光了...");
break;
}
}
}
}
public class Demo1 {
public static void main(String[] args) {
//创建线程对象
BankThread thread1 = new BankThread("老公");
BankThread thread2 = new BankThread("老婆");
//调用start方法启动线程。
thread1.start();
thread2.start();
}
}
自定义线程的创建方式:
方式一:
1. 自定义一个类继承Thread.
2. 子类重写run方法,把自定义线程的任务定义在run方法上。
3. 创建thread子类的对象,并且调用start方法开启线程。
方式二:
1. 自定义一个类去实现Runnable接口。
2. 实现了Runnable接口的run方法, 把自定义线程的任务定义在run方法上。
3. 创建Runnable实现类的对象。
4. 创建Thread对象,并且把Runnable实现类对象作为参数传递进去。
5. 调用thread对象的start方法开启线程。
疑问1: Runnable实现类对象是线程对象吗?
runnable实现类的对象并不是一个线程对象,只不过是实现了Runnable接口的对象而已。
疑问2: 为什么要把Runnable实现类的对象作为参数传递给thread对象呢?作用是什么?
作用: 是把Runnable实现类的对象的run方法作为了任务代码去执行了。
推荐使用: 推荐使用第二种。 因为java是单继承的。
public class Demo3 implements Runnable{
@Override
public void run() {
for(int i = 0 ; i< 100 ; i++){ System.out.println(Thread.currentThread().getName()+":"+i);
}
System.out.println("当前线程对象:"+Thread.currentThread()); // 当前线程对象是: Thread
System.out.println("当前对象:"+ this); //this对象: Demo3的对象
}
public static void main(String[] args) {
//创建Runnable实现类的对象
Demo3 d = new Demo3();
//创建Thread对象,并且把Runnable实现类对象作为参数传递进去
Thread t = new Thread(d,"狗娃");
//调用thead对象的start方法开启线程。
t.start();
/*
1. Thread类使用了target变量记录了Runnable实现类对象。
public void run() { //run方法的代码是属于线程的任务代码
if (target != null) {
target.run(); // 调用了Runanble实现类对象的run方法。 就是相当于把Runnable实现类的run方法作为了线程的任务代码去执行了。
}
}
*/
//主线程执行的。
for(int i = 0 ; i< 100 ; i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}这里写代码片
/*
需求:使用线程的第二种创建方式实现买票的例子
*/
class SaleTickets implements Runnable{
int num = 50; //非静态成员变量
@Override
public void run() { //this
while(true){
synchronized ("锁") {
if(num>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+ num+"号票");
num--;
}else{
System.out.println("售罄了...");
break;
}
}
}
}
}
public class Demo4 {
public static void main(String[] args) {
//创建Runnable实现类的对象
SaleTickets saleTickets = new SaleTickets();
//创建三个线程对象
Thread t1 = new Thread(saleTickets,"窗口1");
Thread t2 = new Thread(saleTickets,"窗口2");
Thread t3 = new Thread(saleTickets,"窗口3");
//调用start方法开启线程
t1.start();
t2.start();
t3.start();
}
}这里写代码片