1.线程的特点
进程是由一个或多个线程组成的
1.抢占式运行【重要】
给程序分配CPU,按照时间来执行,单位时间片抢占式执行
2.资源共享
同一进程,有多个线程,这多个线程是可以共享同一数据的
2.线程的几种状态
1.新建(New)
创建后尚未启动。
2.可运行(Runnable)
可能正在运行,也可能正在等待 CPU 时间片。
包含了操作系统线程状态中的 Running 和 Ready。
3.阻塞(Blocking)
等待获取一个排它锁,如果其线程释放了锁就会结束此状态。
4.等待(Waiting)
等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。
5.死亡(Terminated)
可以是线程结束任务之后自己结束,或者产生了异常而结束。
3.线程的创建方式
1.继承Thread方法
需要实现 run() 方法,因为 Thread 类也实现了 Runable 接口。
当调用 start() 方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的 run() 方法。
package com.qf.xiancheng;
class Test extends Thread{
@Override
public void run() {
System.out.println("线程创建");
}
}
public class Demo01 {
public static void main(String[] args) {
Test test = new Test();
test.run();
new Thread(() -> System.out.println("lambda表达式")).start();
}
}
2.实现Runnable方法(最常用)
需要实现 run() 方法。
通过 Thread 调用 start() 方法来启动线程。
package com.qf.xiancheng;
class Test1 implements Runnable{
@Override
public void run() {
System.out.println("实现Runnable方法的线程");
}
}
public class Demo02 {
public static void main(String[] args) {
Test1 test1 = new Test1();
test1.run();
((Runnable) () -> System.out.println("Lambda方法")).run();
}
}
3.实现 Callable 接口
与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。
package com.qf.xiancheng;
import java.util.concurrent.Callable;
class Test2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 233;
}
}
public class Demo03 {
public static void main(String[] args) throws Exception {
Test2 test2 = new Test2();
System.out.println(test2.call());
}
}
实现接口 VS 继承 Thread
实现接口会更好一些,因为:
Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
类可能只要求可执行就行,继承整个 Thread 类开销过大。
4.线程Thread的方法
Thread()
分配一个Thread对象
Thread(Runnable target)
分配一个新的Thread对象。
Thread(Runnable target,String name)
分配一个新的Thread对象,并对这个线程起一个名字
方法:
返回类型 | 方法说明 |
---|---|
static Thread | currentThread() 返回对当前正在执行的线程对象的引用。 |
String | getName() 返回此线程的名称。 |
void | setName(String name) 将此线程的名称更改为等于参数 name 。 |
int | getPriority() 返回此线程的优先级。 |
void | setPriority(int newPriority) 更改此线程的优先级。 |
static void | sleep(long millis) 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。 |
package com.qf.xiancheng;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
class Test3 implements Runnable {
@Override
public void run() {
Thread thread = Thread.currentThread();//获取当前线程的名字(系统默认)
System.out.println(thread);//打印
}
}
class Test4 implements Runnable{
@Override
public void run() {
System.out.println("test4");
String name=Thread.currentThread().getName();
System.out.println(name);
}
}
public class Demo04 {
public static void main(String[] args) {
Thread thread = new Thread(new Test3());
thread.start();
Thread test4 =new Thread(new Test4());
test4.setName("自己定义的线程名字");
test4.start();
Thread thread1=new Thread(new Test3(),"线程名");
System.out.println(thread1.getName());
Thread thread2=new Thread(new Test4());
System.out.println(thread2.getPriority());//获取线程的优先级,默认为5
Thread thread3 = new Thread(new Test3());
thread3.setPriority(8);//自定义线程优先级1到10,数越大优先级越高,但是执行时仍然是抢占式的
System.out.println(thread3.getPriority());
}
}
package com.qf.xiancheng;
class Test5 implements Runnable{
@Override
public void run() {
try {
Thread.sleep(10000);//sleep只能用try-catch不能throw
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("先睡10秒才会输出");
}
}
public class Demo05 {
public static void main(String[] args) {
Thread thread = new Thread(new Test5());
thread.start();
}
}
5.线程同步和锁
当多个线程同时运行时,线程的调度由操作系统决定,程序本身无法决定。因此,任何一个线程都有可能在任何指令处被操作系统暂停,然后在某个时间段后继续执行。
这个时候,有个单线程模型下不存在的问题就来了:如果多个线程同时读写共享变量,会出现数据不一致的问题。
线程同步模拟:
package com.qf.xiancheng;
//这是一个模拟线程同步的例子
//没有对线程加锁,两个窗口出售100张票
//不加锁会出现错误情况
class SaleTicket implements Runnable{
private int ticket=100;
@Override
public void run() {
while (true){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+ticket+"张票");
ticket--;
}else {
System.out.println("票卖完了");
break;
}
}
}
}
public class Demo06 {
public static void main(String[] args) {
//两个线程操作同一个数据
Thread thread = new Thread(new SaleTicket());
new Thread(thread,"线程1").start();
new Thread(thread,"线程2").start();
/*
线程1卖出了第100张票
线程2卖出了第100张票
线程1卖出了第99张票
线程2卖出了第98张票
*/
}
}
通过上面的例子可以发现,虽然是两个线程操作统一数据,但是却会出现,了两个窗口卖出了同一张票。当线程1还未进行ticket–时,线程2此时获得了ticket
对线程进行加锁,保证同一时间只有一个线程可以对数据进行操作
解决办法:将买票的代码写到synchronized中
同步代码块: 将一段代码放到synchronized 然后括起来。就会对这段代码加上锁。
synchronized (this) {
}
package com.qf.xiancheng;
//这是一个模拟线程同步的例子
//没有对线程加锁,两个线程出售100张票
//不加锁会出现错误情况
class SaleTicket1 implements Runnable{
private int ticket=100;
@Override
public void run() {
while (true){
synchronized (this){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+ticket+"张票");
ticket--;
}else {
System.out.println("票卖完了");
break;
}
}
}
}
}
public class Demo07 {
public static void main(String[] args) {
//两个线程操作同一个数据
Thread thread = new Thread(new SaleTicket1());
new Thread(thread,"线程1").start();
new Thread(thread,"线程2").start();
}
}
synchronized 被成为隐式锁,会自动释放,是非公平的锁。
Lock锁 被称为显示锁。
他们两个锁都可以解决线程同步的问题。但是synchronized 更加灵活。所以一般开发时候用synchronized 。以后还会有线程池,也有锁更高级。
Lock是一个接口,实现ReentrantLock
有两个重要方法
lock();
unlock();
package com.qf.d_thread;
import java.util.concurrent.locks.ReentrantLock;
class SaleTicket1 implements Runnable {
private int ticket = 100;
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try{
lock.lock();
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "票");
ticket--;
} else {
System.out.println("票已经买完了");
break;
}
} catch (Exception e) {
} finally {
lock.unlock();
}
}
}
}
public class Demo2 {
public static void main(String[] args) {
//强调的是:多个线程操作同一个数据 ticket
SaleTicket1 saleTicket = new SaleTicket1();
new Thread(saleTicket, "线程1").start();
new Thread(saleTicket, "线程2").start();
}
}
在Java 中,死锁(Deadlock)情况是指:两个或两个以上的线程持有不同系统资源的锁,线程彼此都等待获取对方的锁来完成自己的任务,但是没有让出自己持有的锁,线程就会无休止等待下去。 线程竞争的资源可以是:锁、网络连接、通知事件,磁盘、带宽,以及一切可以被称作“资源”的东西
import java.util.Date;
public class LockTest {
public static String obj1 = "obj1";
public static String obj2 = "obj2";
public static void main(String[] args) {
LockA la = new LockA();
new Thread(la).start();
LockB lb = new LockB();
new Thread(lb).start();
}
}
class LockA implements Runnable{
public void run() {
try {
System.out.println(new Date().toString() + " LockA 开始执行");
while(true){
synchronized (LockTest.obj1) {
System.out.println(new Date().toString() + " LockA 锁住 obj1");
Thread.sleep(3000); // 此处等待是给B能锁住机会
synchronized (LockTest.obj2) {
System.out.println(new Date().toString() + " LockA 锁住 obj2");
Thread.sleep(60 * 1000); // 为测试,占用了就不放
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class LockB implements Runnable{
public void run() {
try {
System.out.println(new Date().toString() + " LockB 开始执行");
while(true){
synchronized (LockTest.obj2) {
System.out.println(new Date().toString() + " LockB 锁住 obj2");
Thread.sleep(3000); // 此处等待是给A能锁住机会
synchronized (LockTest.obj1) {
System.out.println(new Date().toString() + " LockB 锁住 obj1");
Thread.sleep(60 * 1000); // 为测试,占用了就不放
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
上面的代码会产生死锁。
6.什么是线程死锁
死锁是指两个或两个以上进程(线程)在执行过程中,由于竞争资源或由于彼此通信造成的一种堵塞的现象,若无外力的作用下,都将无法推进,此时的系统处于死锁状态。
7.守护线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕
如,后台记录操作日志,监控内存,垃圾回收等待。。。
守护线程是用来守护非守护线程的,当非守护线程结束时,守护线程也会马上结束
package com.qf.xiancheng;
public class Demo08 {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
thread.setDaemon(true);//默认为flase 为用户线程, true为守护线程
thread.start();
new Thread(you).start();
}
}
class God implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("上帝守护着你(守护线程)-------");
}
}
}
class You implements Runnable {
@Override
public void run() {
for (int i = 0; i < 365; i++) {
System.out.println("开心着活着每一天(非守护线程)------");
}
System.out.println("----goodbye!Beautiful World!!!------");
}
}