1、run方法(需要重写)
(1):run方法就是线程要执行任务的方法
(2):启动线程不需要调用run方法,而是调用threa里的start方法来启动任务
(3):main方法中的叫做主线程,它和其他线程同时执行,其中谁先执行,谁后执行是不一定的(两个线程强占时间分配)看例子:
package Day1;
public class Demo1 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i=0;i<10;i++){
System.out.println("锄禾日当午"+i);
}
}
}
package Day1;
public class MyThread extends Thread{
public void run(){
//需要重写run方法
for (int i=0;i<10;i++){
System.out.println("汗滴禾下土"+i);
}
}
}
我们可以发现两次的结果是不一样的。
(4):每个线程都有自己的栈空间,共用一份堆内存
(1):创建一个任务对象(用Runnable创建)
(2):创建一个线程并为其分配任务(还要用到Thread创建对象,并将创建的任务对象传给Thread)
(3):执行这个程序(线程对象.start方法)
看例子:
package Day1;
public class Demo1 {
public static void main(String[] args) {
//创建任务对象
MyRunnanle myRunnanle = new MyRunnanle();
//创建一个线程,并讲任务赋给线程
Thread thread = new Thread(myRunnanle);
//执行任务
thread.start();
//主线程
for (int i=0;i<10;i++){
System.out.println("锄禾日当午"+i);
}
}
}
package Day1;
public class MyRunnanle implements Runnable{
public void run(){
//创建一个任务
for (int i=0;i<10;i++){
System.out.println("汗滴禾下土"+i);
}
}
}
同样是两个线程同时进行
1:通过创建任务,然后给线程分配的方式来实现多继承,更适合多个线程同时执行相同任务的情况
2:可以避免单继承所带来的局限性
3:线程与任务本身是分离的,提高了程序的健壮性
4:线程池技术只支持Runnable类型的任务,不接收Thread类型的线程
获取当前线程的方法
Thread.currentThread();
获取线程名称的方法为:
Thread.currentThread().getName();
设置现成的名称
Thread.currentThread().getName();
看例子
package Day1;
public class Demo1 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
System.out.println(new Thread(new MyRunnanle(),"哈哈哈").getName());
System.out.println(new Thread(new MyRunnanle()).getName());
Thread thread = new Thread(new MyRunnanle());
thread.setName("nihao");
System.out.println(thread.getName());
}
}
package Day1;
public class MyRunnanle implements Runnable{
public void run(){
//创建一个任务
for (int i=0;i<10;i++){
System.out.println("汗滴禾下土"+i);
}
}
}
Thread.sleep(millis:1000)休眠一秒钟参数可以是毫秒,也可以是毫秒加纳秒
理解为消耗时间的部分例如:接受用户输入,读取文件内容。线程阻塞也称之为耗时操作
线程中断标记:
Thread.interruput();如果出现了中断标记则就会进入catch语句,我们如果想终断线程的话就需要再catch块中添加return语句就会将线程终止。
看例子:
package Day1;
public class Demo1 {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnanle(),"线程");
thread.start();
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
thread.interrupt();
}
}
package Day1;
public class MyRunnanle implements Runnable{
public void run(){
//创建一个任务
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//e.printStackTrace();
System.out.println("我们发现中断标记,所以我们选择退出线程");
return;
}
}
}
}
线程的分类:
用户 线程:当一个进程不包含任何一个存活的用户线程时,进程结束。
守护线程:守护用户进程的,当最后一个用户进程结束时 ,所有守护线程自动死亡。
1、将一个线程设置为守护线程:
Thread thread = new Thread(new MyRunnable);
thread.setDaemon(true);
//一定要在线程开始前将其设置为守护线程
thread.start();
主线程结束之后无论守护线程是否结束,都会跟着死亡。
我们直接看这样一段代码:
package Day1;
public class Demo1 {
public static void main(String[] args) {
Runnable runnable = new Ticket();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
}
package Day1;
public class Ticket implements Runnable{
int count = 10;
public void run(){
while (count >0){
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count --;
System.out.println("出票成功,余票"+count);
}
}
}
看运行结果:
最后结果出现了-2.。
解释原因:
在count=1时第一个线程抢到时间片,但是因为遇到睡眠所以时间片丢失,被令外两个线程抢到。所以三个线程都满足count>0的条件所以都会买到票,随后count减去三就是-2.
线程排队执行(枷锁机制)
线程同步:synchronized
格式:synchronized(锁对象){ 同步代码块 }
注意:锁对象可以是任何Java对象
锁对象的描述:
当线程开始之后会将任务的一部分加上锁标记,然后当其他线程开始这个任务时发现有锁标记,就会暂停,进行等待,当这个任务结束时,会将锁标记解除,然后多个线程就会去抢这个锁,谁抢到谁就执行这个任务,后面的线程在排队。特别注意:一定时多个线程看同一把锁,不能是一个线程都有一个自己的锁,这样时没有用的。(锁对象不能创建在run方法中,最好创建在任务类中)接下来我们看一个卖票的例子:
package Day1;
public class Demo1 {
public static void main(String[] args) {
Runnable ticket = new Ticket();
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
}
package Day1;
public class Ticket implements Runnable{
int count = 10;
private Object o = new Object();
public void run(){
while (true){
synchronized (o){
if (count >0){
System.out.println("正在准备卖票");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
count --;
System.out.println(Thread.currentThread().getName()+"出票成功,余票"+count);
}else {
break;
}
}
}
}
}
这个时候就不会出现余票为负数的情况。
同步方法就是将同步代码块抽成了一个方法,然后将这个方法用synchronized修饰
package Day1;
public class Demo1 {
public static void main(String[] args) {
Runnable ticket = new Ticket();
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
}
package Day1;
public class Ticket implements Runnable{
int count = 10
public void run(){
while (true){
int flag = sale();
if(!flag){
break;
}
}
}
public synchronized boolean sale(){
//锁对象是this,就是调用这个方法的对象
//如果是静态的锁对象是Ticket.class()
if (count >0){
System.out.println("正在准备卖票");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
count --;
System.out.println(Thread.currentThread().getName()+"出票成功,余票"+count);
return true;
}else {
return false;
}
}
}
在这里要注意的是呢经过synchronized修饰的方法的锁对象就是this
如果使用static修饰的方法锁对象就是类名.class
语法格式:private Lock l = new RenntrantLock();创建一个锁对象
l.lock();在需要排队的代码前面上锁。
l.unlock();解锁。
公平锁:先来先到。
非公平锁:Java中默认的都是非公平锁:线程同时抢,谁抢到就是谁的
语法:
private Lock l = new RenntrantLock(true);//传入true就是公平锁,本身默认就是false。
就是两个线程卡住的情况 : 两个线程锁住两块资源,但是A线程想要使用B线程的资源,同时B线程呢也想要使用A线程的资源,这时候两边的线程都在等待资源的释放,所以两个线程都进行不下去。看一个警察与绑匪的例子:
package Day1;
import org.w3c.dom.ls.LSOutput;
public class Demo2 {
//线程死锁
public static void main(String[] args) {
Culprit c = new Culprit();
Police p = new Police();
new MyThread(c,p).start();
c.say(p);
}
static class MyThread extends Thread{
private Culprit c;
private Police p;
MyThread(Culprit c,Police p){
this.c=c;
this.p=p;
}
@Override
public void run() {
p.say(c);
}
}
static class Culprit{
public synchronized void say(Police p){
System.out.println("你放了我,我放人质");
p.fun();
}
public synchronized void fun(){
System.out.println("罪犯被放了,罪犯也放了人质");
}
}
static class Police{
public synchronized void say(Culprit c){
System.out.println("你放了人质,我放了你");
c.fun();
}
public synchronized void fun(){
System.out.println("警察救了人质,罪犯跑了");
}
}
}
因为Mythread线程锁定了p对象,主线程锁定了c对象,所以Mytheread不能调用c对象的fun方法,同时主线程也不能调用p对象的fun方法,两边都在等,所以线程锁死
注意:这里锁死的是对象,是对象p和对象c,而不是对象里面的方法,这样就好理解了。
这个问题可以用一个厨师与服务员的例子讲述一下:
厨师的线程是做饭。
服务员的线程是换取盘子端走饭。
那么这两个线程是要交替执行的:厨师做饭的时候服务员歇着。服务员端饭的时候厨师歇着(可能不是那么贴合,这么理解就完事了)
那么需要用到两个方法:
this.notifyAll()唤醒线程
this.wait();让线程睡着
这个地方本人属实讲的不是很清楚,重点理解这个思想就行了。
1
编写类实现Callable接口 , 实现call方法
class XXX implements Callable<T> {
@Override
public <T> call() throws Exception {
return T;
}
}
创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask<Integer> future = new FutureTask<>(callable);
通过Thread,启动线程 new Thread(future).start();
1、缓存线程池
/*** 缓存线程池.* (长度无限制)* 执行流程:*
1. 判断线程池是否存在空闲线程*
2. 存在则使用*
3. 不存在,则创建线程 并放入线程池, 然后使用*/
ExecutorService service = Executors.newCachedThreadPool();//向线程池中加入新的任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName()); } });
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的称:"+Thread.currentThread().getName()); } });
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName()); } });
2、 定长线程池
3. 单线程线程池
4. 周期性任务定长线程池这些就不一一列举了。并非重点。