一、Thread类的构造器和方法们
1、构造器
Thread()
Tread(String name)
2、Thread类的方法:
(1)public void run (){}子类会重写它,它是线程完成任务的方法
(2)public void start() 重启线程的方法
(3)public String getName():获取线程名字
自定义线程的默认名称是: Thread -编号,修改名称可以调用setName(String name)或者是在创建线程是用构造器为name赋值
(4)public static Thread currentThread():获取当前线程对象
(5)public int getPriority():获取线程的优先级
public void setPriority(int):获取线程的优先级
优先级:优先级越高,那么CPU调用它的概率越高。
优先级的范围:MIN_PRIORITY ~ MAX_PRIORITY范围内
MIN_PRIORITY:1
MAX_PRIORITY:10
NORM_PRIORITY:5
(6)public final boolean isAlive():判断线程是否还活着
public class TestThread {
public static void main(String[] args) {
MyThread my = new MyThread("线程1");
// my.setPriority(100); //IllegalArgumentException非法参数异常
my.start();
/* MyThread my2 = new MyThread();
my2.start();*/
for(int i=2; i<=100; i+=2){
System.out.println(Thread.currentThread().getName() + ":" + i);
//因为这句代码是main线程执行的,那么Thread.currentThread()获取的就是main线程
}
}
}
class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for(int i=1; i<=100; i+=2){
System.out.println(getName() + ":" + i);//getName()从Thread父类继承的
}
}
}
3、Thread类的方法:
(7)public static void sleep(long millis):线程休眠,单位是毫秒
public static void sleep(long millis,int nanos):线程休眠单位是毫秒和纳秒
(8)public static void yield():暂时当前正在执行的线程对象,并执行其他线程
yield()只是说当前线程象征性的谦让一下,不代表CPU一定会让给其他人。
(9)public void join():加塞
public void join(时间):制定加塞时间
(10)public final void stop():停止线程,已过时
public class TestThread2 {
public static void main(String[] args) throws InterruptedException {
/* for(int i=10; i>0; i--){
System.out.println(i);
Thread.sleep(1000);
// TimeUnit.SECONDS.sleep(1);
// TimeUnit.MINUTES.sleep(1);
}*/
TheadDemo demo = new TheadDemo();
demo.start();
TheadDemo demo2 = new TheadDemo();
demo2.start();
for (int i=11; i<=20; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
if(i==11){
// Thread.yield();
//demo.join(); //demo线程不结束,当前线程main就没机会了 demo把main线程阻塞了
//demo和demo2不会出现阻塞现象,还是互抢CPU
demo.join(5);//5毫秒之后,main就自由了
}
}
}
}
class TheadDemo extends Thread{
public void run(){
for(int i=1; i<=10; i++){
System.out.println(getName() + ":" + i);
}
}
}
4、守护线程
(1)角色1:干正事的线程
(2)角色2:服务于角色1的线程,称为守护线程
(3)语法格式
Thread类中有一个方法:public final void setDaemon(boolean on) 如果为 true,则将该线程标记为守护线程
二、线程安全问题
1什么是线程安全问题?
当多个线程“同时”使用同一个“共享”数据时,可能就会有线程安全问题。
如果一些线程进行“读/查看”操作,一些线程进行“写/修饰”操作,这个才会出现线程安全问题。
如果所有线程都是进行的“读”操作,那么是不会有线程安全问题的。
共享数据可以是同一个变量或同一个文件或同一个表中的同一条记录等。
知识点:哪些内存是线程之间可以共享的,哪些内存是线程之间独立的。
2.JVM的内存:
(1)方法区
(2)堆
(3)栈(包括虚拟机栈和本地方法栈)
(4)程序计数器:CPU在多个线程之间切换时,程序计数器就要记录每一个线程当前执行到哪里,下一条指令是什么。
其中(3)(4)一定是线程独立。
(1)和(2)是多个线程可以共享的。但是如果是堆的话,必须保证是同一个对象。
3、如何解决
加锁
在SE阶段,使用的是同步锁(synchronized)
在高级阶段,使用的Lock锁(juc:java.util.concurrent )
语法:
(1)同步代码块
(2)同步方法
4、同步代码快
语法格式:
synchronized(锁对象){
需要加锁的代码}
要加在循环里边,判断条件外边
要求:
(1)锁对象可以是任意类型的对象,但是必须要求多个线程使用同一个锁对象
(2)锁的范围太大了不行,太小也不行
一般考虑单次任务设计的代码有哪些?
比如卖票,单次任务
①条件判断是否有票
②票数减少
③查看剩余票
*/
public class TestSafe1 {
public static void main(String[] args) {
Window w1 =new Window("窗口一");
Window w2 =new Window("窗口二");
Window w3 =new Window("窗口三");
//每个窗口卖出了10张票
w1.start();
w2.start();
w3.start();
}
//这里声明为静态成员内部类是为了避免重名问题,在同一个包中
static class Window extends Thread{
public Window(String name) {
super(name);
}
public void run(){
int total = 10;//局部变量存储在栈中
while(total>0){
total--;
System.out.println(getName() + "卖出一张票,剩余" +total + "张");
}
}
}
}
5、同步方法
语法格式:
【其他修饰符】synchronized 返回值类型 方法名(【形参列表】)【throws 异常列表】{
}
要求:
(1)锁对象,多个线程必须是同一个锁对象
非静态方法锁对象,是this
静态方法,锁对象,是当前类的class对象
(2)范围
举例:卖票
实际开发中,一般写多线程的程序是怎么写?
(1)先写一个资源类,例如:票
(2)创建资源类的对象
(3)其他多个线程使用资源类的对象
public class TestSynchronizedMethod1 {
public static void main(String[] args) {
//创建资源类的对象
Ticket ticket = new Ticket();
new Thread("窗口一"){
public void run(){
while(ticket.getTotal()>0){
ticket.sale();
}
}
}.start();
new Thread("窗口二"){
public void run(){
while(ticket.getTotal()>0){
ticket.sale();
}
}
}.start();
new Thread("窗口三"){
public void run(){
while(ticket.getTotal()>0){
ticket.sale();
}
}
}.start();
}
//资源类
static class Ticket{
private int total = 1000;//总票数
//调用一次sale方法,卖一张票
//非静态方法,锁对象是this,这里的this是同一个对象
public synchronized void sale(){
if(total>0) {//双重保险
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余:" + total);
}
}
public int getTotal() {
return total;
}
}
}
6、单列设计模式
1、什么是设计模式:
解决某一类问题,前人给我们总结出来的代码模板,套路。
设计模式不是Java语言独有的,是适用于所有的开发语言。
一共有23种目前公认比较经典的设计模式。
2、单列设计模式
适用常见:如果你的系统/软件中,某一个类的对象要求在整个系统中只能出现一个。那么这个时候,这个类就应该用单例设计模式去设计。
比如:
(1)军事系统,总司令对象只有一个
(2)手机系统:时钟对象只有一个
(3)JavaEE系统
web服务器tomcat容器的容器对象,对于每一个Web项目来说只有一个。
spring框架,它的总的管理器对象只有一个。
…3、如何实现单例设计模式
(1)饿(恶)汉式单例设计模式
无论你什么时候用到这个对象,只要用到这个类了,就上来先创建这个类的唯一对象。“着急”,在类初始化时就创建了对象。
饥不择食
提前创建了,后面用的时候,直接用了,就快。
前慢后快。
(2)懒汉式单例设计模式
只有你真的需要这个类的对象了,我才不得已给你创建它,你不用我就先不给你创建。
懒这个设计在后面很多地方也都有用到,例如:后面会提到的懒加载等,延迟初始化等。
因为内存,性能都非常宝贵,如果你不用,确把相关的对象弄出来了,占着内存,消耗性能。
前快后用的时慢。
public class TestSingleton {
@Test
public void test01(){
//获取Hungry1的唯一对象
Hungry1 obj1 = Hungry1.INSTANCE;
Hungry1 obj2 = Hungry1.INSTANCE;
System.out.println(obj1 == obj2);
}
@Test
public void test02(){
//获取Hungry2的唯一对象
Hungry2 obj1 = Hungry2.INSTANCE;
Hungry2 obj2 = Hungry2.INSTANCE;
System.out.println(obj1 == obj2);
}
@Test
public void test03(){
//获取Hungry2的唯一对象
Hungry3 obj1 = Hungry3.getINSTANCE();
Hungry3 obj2 = Hungry3.getINSTANCE();
System.out.println(obj1 == obj2);
}
@Test
public void test04(){
Runtime currentRuntime = Runtime.getRuntime();//获取唯一的对象
System.out.println("总内存:" + currentRuntime.totalMemory());
System.out.println("空闲内存:" + currentRuntime.freeMemory());
}
@Test
public void test05(){
Lazy1 obj1 = Lazy1.getInstance();
Lazy1 obj2 = Lazy1.getInstance();
System.out.println(obj1 == obj2);
}
//多线程环境
Lazy1 obj1;
Lazy1 obj2;
@Test
public void test06(){
Thread t1 = new Thread() {
public void run() {
obj1 = Lazy1.getInstance();
}
};
Thread t2 = new Thread() {
public void run() {
obj2 = Lazy1.getInstance();
}
};
t1.start();
t2.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(obj1);
System.out.println(obj2);
System.out.println(obj1 == obj2);
}
@Test
public void test07(){
// Lazy3.method();
Lazy3 obj = Lazy3.getInstance();
}
}
//饿汉式的写法(一)
class Hungry1{
//(2)用一个静态的常量保存这个类对象
public static final Hungry1 INSTANCE = new Hungry1();
//(1)构造器私有化
private Hungry1(){}
}
//饿汉式的写法(二)
enum Hungry2{
INSTANCE
}
//饿汉式的写法(三),例如系统类中就有一个这样的代表 Runtime
//java.lang.Runtime:表示当前JVM的运行时环境对象
class Hungry3{
//(2)用一个静态的常量保存这个类对象
private static final Hungry3 INSTANCE = new Hungry3();
//(1)构造器私有化
private Hungry3(){}
//(3)提供get方法
public static Hungry3 getINSTANCE() {
return INSTANCE;
}
}
//懒汉式的写法(一)
class Lazy1{
private static Lazy1 instance;
//(1)构造器私有化
private Lazy1(){}
//(2)获取唯一对象的方法
/* public static Lazy1 getInstance(){
return new Lazy1();//错误,每次调用都new新的
}*/
public synchronized static Lazy1 getInstance(){
if(instance == null){
instance = new Lazy1();
}
return instance;
}
}
//在Lazy1的基础上,进行优化
class Lazy2{
private static Lazy2 instance;
//(1)构造器私有化
private Lazy2(){}
//(2)获取唯一对象的方法
public static Lazy2 getInstance(){
if(instance == null) {//提高效率
synchronized (Lazy2.class) {//Lazy2.class是得到当前类的Class对象
if (instance == null) {//安全
instance = new Lazy2();
}
}
}
return instance;
}
}
//提出了一种新的写法
class Lazy3{
/*static{
System.out.println("外部类的静态代码块");
}*/
//(1)构造器私有化
private Lazy3(){}
//静态内部类
/*
静态内部类的加载不会随着外部类的加载而加载
静态内部类也是第一次使用它时加载
*/
static class Inner{
private static final Lazy3 INSTANCE = new Lazy3();
/*static{
System.out.println("内部类的静态代码块");
}*/
}
public static Lazy3 getInstance(){
return Inner.INSTANCE;
}
/* public static void method(){
System.out.println("外部类的静态方法");
}*/
}
7、线程通信
用于解决生产者与消费者问题
1、生产者与消费者问题
当有多个线程一起工作,一些线程是往“数据缓冲区”添加数据,称为生产者,一些线程是从“数据缓冲区”取/消耗数据,称为消费者。
此时,因为我们的“数据缓冲区”是有限的,那么当“数据缓冲区”满的时候,生产者线程应该“停”下来,等消费者消耗了数据之后,它再接着生产。
同理,当“数据缓冲区”空的时候,消费者线程应该“停”下来,等生产者生产了新的数据之后,它再接着消费。
两个问题:
(1)线程安全问题
多个线程,共享数据,有读有写操作
说明一定会用到synchronized或Lock
(2)线程的通信问题–>等待与唤醒问题
2、如何实现线程的等待与唤醒呢?
(1)wait(),wait(时间):线程等待
(2)notify()/notifyAll():线程唤醒/通知
notify():只会唤醒1个
notifyAll():唤醒所有等待的,被唤醒的如果需要接着wait,如果条件不成立,就可以干活了
要求:
wait和notify方法必须由“锁”对象调用,否则就报IllegalMonitorStateException异常。
3、举例
小饭馆,厨房与前厅之间有个小窗口,小窗口上有个小平台,上面最多可以放10盘菜。
现在饭馆营业。
开发:厨师
翠花:服务员
数据缓冲区:窗口的小平台
共享数据:菜
public class TestCommunicate {
public static void main(String[] args) {
Workbench workbench = new Workbench();
Cook cook = new Cook("开发",workbench);
Waiter waiter = new Waiter("翠花",workbench);
cook.start();
waiter.start();
}
}
class Cook extends Thread{
private Workbench workbench;
public Cook(String name, Workbench workbench) {
super(name);
this.workbench = workbench;
}
public void run(){
while(true) {
workbench.put();
}
}
}
class Waiter extends Thread{
private Workbench workbench;
public Waiter(String name, Workbench workbench) {
super(name);
this.workbench = workbench;
}
public void run(){
while(true) {
workbench.take();
}
}
}
//资源类
class Workbench{
private static final int MAX = 10;
private int total;
//厨师调用
public synchronized void put(){ //非静态方法的锁对象,默认是this
if(total >= MAX){
try {
//当前线程停下来
this.wait();//this是当前线程的锁对象,即监视器对象
} catch (InterruptedException e) {
e.printStackTrace();
}
}
total++;
System.out.println(Thread.currentThread().getName() + "做好了一盘菜,现在有:" + total);
this.notify();
}
//服务员调用
public synchronized void take(){
if(total <= 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
total--;
System.out.println(Thread.currentThread().getName() + "取走了一盘菜,现在有:" + total);
this.notify();
}
}
三、关键字
1.volatile:
是告诉系统不要缓存这个变量
2,interrupt方法
是让休眠的线程停止休眠
五、进程时间
语法格式:
long 变量 = System.currentTimeMillis();
public class TestSystem {
public static void main(String[] args) {
long timeMillis = System.currentTimeMillis();
System.out.println(timeMillis);//1599620217594
/*
1599620217594值是当前时间与协调世界时 1970 年 1 月 1 日午夜之间的时间差(以毫秒为单位测量)。
1599620265190值是当前时间与协调世界时 1970 年 1 月 1 日午夜之间的时间差(以毫秒为单位测量)。
*/
}
}